From 533b938fcd330bd920fad6a2c55ae8c38083534f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 17 Jan 2019 08:19:40 +0100 Subject: [PATCH 01/27] Removed baseColor --- .../ThresholdsEditor/ThresholdsEditor.tsx | 21 +++++---- .../plugins/panel/gauge/GaugePanelOptions.tsx | 2 - public/app/plugins/panel/gauge/types.ts | 1 - public/app/viz/Gauge.test.tsx | 9 ++-- public/app/viz/Gauge.tsx | 47 +++++++++---------- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx index 6e8d88051f3..bd4f83f4dba 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx @@ -19,9 +19,15 @@ export class ThresholdsEditor extends PureComponent { constructor(props: Props) { super(props); - const thresholds: Threshold[] = - props.thresholds.length > 0 ? props.thresholds : [{ index: 0, value: -Infinity, color: colors[0] }]; + const addDefaultThreshold = this.props.thresholds.length === 0; + const thresholds: Threshold[] = addDefaultThreshold + ? [{ index: 0, value: -Infinity, color: colors[0] }] + : props.thresholds; this.state = { thresholds }; + + if (addDefaultThreshold) { + this.onChange(); + } } onAddThreshold = (index: number) => { @@ -62,7 +68,7 @@ export class ThresholdsEditor extends PureComponent { }, ]), }, - () => this.updateGauge() + () => this.onChange() ); }; @@ -85,7 +91,7 @@ export class ThresholdsEditor extends PureComponent { thresholds: newThresholds.filter(t => t !== threshold), }; }, - () => this.updateGauge() + () => this.onChange() ); }; @@ -124,11 +130,10 @@ export class ThresholdsEditor extends PureComponent { { thresholds: newThresholds, }, - () => this.updateGauge() + () => this.onChange() ); }; - onChangeBaseColor = (color: string) => this.props.onChange(this.state.thresholds); onBlur = () => { this.setState(prevState => { const sortThresholds = this.sortThresholds([...prevState.thresholds]); @@ -139,10 +144,10 @@ export class ThresholdsEditor extends PureComponent { return { thresholds: sortThresholds }; }); - this.updateGauge(); + this.onChange(); }; - updateGauge = () => { + onChange = () => { this.props.onChange(this.state.thresholds); }; diff --git a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx index 9729416b7e6..18a445d840d 100644 --- a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx +++ b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx @@ -1,6 +1,5 @@ import React, { PureComponent } from 'react'; import { - BasicGaugeColor, PanelOptionsProps, ThresholdsEditor, Threshold, @@ -15,7 +14,6 @@ import { GaugeOptions } from './types'; export const defaultProps = { options: { - baseColor: BasicGaugeColor.Green, minValue: 0, maxValue: 100, prefix: '', diff --git a/public/app/plugins/panel/gauge/types.ts b/public/app/plugins/panel/gauge/types.ts index b698a3389c2..42262178dc8 100644 --- a/public/app/plugins/panel/gauge/types.ts +++ b/public/app/plugins/panel/gauge/types.ts @@ -1,7 +1,6 @@ import { Threshold, ValueMapping } from '@grafana/ui'; export interface GaugeOptions { - baseColor: string; decimals: number; valueMappings: ValueMapping[]; maxValue: number; diff --git a/public/app/viz/Gauge.test.tsx b/public/app/viz/Gauge.test.tsx index 69c7733f44b..2678b3f2ad1 100644 --- a/public/app/viz/Gauge.test.tsx +++ b/public/app/viz/Gauge.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { BasicGaugeColor, TimeSeriesVMs } from '@grafana/ui'; +import { TimeSeriesVMs } from '@grafana/ui'; import { Gauge, Props } from './Gauge'; @@ -10,7 +10,6 @@ jest.mock('jquery', () => ({ const setup = (propOverrides?: object) => { const props: Props = { - baseColor: BasicGaugeColor.Green, maxValue: 100, valueMappings: [], minValue: 0, @@ -18,7 +17,7 @@ const setup = (propOverrides?: object) => { showThresholdMarkers: true, showThresholdLabels: false, suffix: '', - thresholds: [], + thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }], unit: 'none', stat: 'avg', height: 300, @@ -42,12 +41,12 @@ describe('Get font color', () => { it('should get base color if no threshold', () => { const { instance } = setup(); - expect(instance.getFontColor(40)).toEqual(BasicGaugeColor.Green); + expect(instance.getFontColor(40)).toEqual('#7EB26D'); }); it('should be f2f2f2', () => { const { instance } = setup({ - thresholds: [{ value: 59, color: '#f2f2f2' }], + thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }, { index: 1, value: 59, color: '#f2f2f2' }], }); expect(instance.getFontColor(58)).toEqual('#f2f2f2'); diff --git a/public/app/viz/Gauge.tsx b/public/app/viz/Gauge.tsx index 094e630a1c0..069a8dbcc3c 100644 --- a/public/app/viz/Gauge.tsx +++ b/public/app/viz/Gauge.tsx @@ -6,7 +6,6 @@ import config from '../core/config'; import kbn from '../core/utils/kbn'; export interface Props { - baseColor: string; decimals: number; height: number; valueMappings: ValueMapping[]; @@ -27,7 +26,6 @@ export class Gauge extends PureComponent { canvasElement: any; static defaultProps = { - baseColor: BasicGaugeColor.Green, maxValue: 100, valueMappings: [], minValue: 0, @@ -91,24 +89,25 @@ export class Gauge extends PureComponent { } getFontColor(value) { - const { baseColor, maxValue, thresholds } = this.props; + const { maxValue, thresholds } = this.props; - if (thresholds.length > 0) { - const atThreshold = thresholds.filter(threshold => value <= threshold.value); - - if (atThreshold.length > 0) { - return atThreshold[0].color; - } else if (value <= maxValue) { - return BasicGaugeColor.Red; - } + if (thresholds.length === 1) { + return thresholds[0].color; } - return baseColor; + const atThreshold = thresholds.filter(threshold => value < threshold.value); + + if (atThreshold.length > 0) { + return atThreshold[0].color; + } else if (value <= maxValue) { + return BasicGaugeColor.Red; + } + + return ''; } draw() { const { - baseColor, maxValue, minValue, timeSeries, @@ -137,16 +136,16 @@ export class Gauge extends PureComponent { const thresholdMarkersWidth = gaugeWidth / 5; const thresholdLabelFontSize = fontSize / 2.5; - const formattedThresholds = [ - { value: minValue, color: BasicGaugeColor.Green }, - ...thresholds.map((threshold, index) => { - return { - value: threshold.value, - color: index === 0 ? threshold.color : thresholds[index].color, - }; - }), - { value: maxValue, color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor }, - ]; + // const formattedThresholds = [ + // { value: minValue, color: BasicGaugeColor.Green }, + // ...thresholds.map((threshold, index) => { + // return { + // value: threshold.value, + // color: index === 0 ? threshold.color : thresholds[index].color, + // }; + // }), + // { value: maxValue, color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor }, + // ]; const options = { series: { @@ -164,7 +163,7 @@ export class Gauge extends PureComponent { layout: { margin: 0, thresholdWidth: 0 }, cell: { border: { width: 0 } }, threshold: { - values: formattedThresholds, + values: thresholds, label: { show: showThresholdLabels, margin: thresholdMarkersWidth + 1, From 9dcf3d58ea77cbee729fd26022ce83bdcc2358d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 17 Jan 2019 09:54:31 +0100 Subject: [PATCH 02/27] Fixed getFontColor, added tests and fixed thresholds logic --- public/app/viz/Gauge.test.tsx | 28 ++++++++++++++++++++++------ public/app/viz/Gauge.tsx | 35 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/public/app/viz/Gauge.test.tsx b/public/app/viz/Gauge.test.tsx index 2678b3f2ad1..3fed641c9a2 100644 --- a/public/app/viz/Gauge.test.tsx +++ b/public/app/viz/Gauge.test.tsx @@ -38,17 +38,33 @@ const setup = (propOverrides?: object) => { }; describe('Get font color', () => { - it('should get base color if no threshold', () => { - const { instance } = setup(); + it('should get first threshold color when only one threshold', () => { + const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] }); - expect(instance.getFontColor(40)).toEqual('#7EB26D'); + expect(instance.getFontColor(49)).toEqual('#7EB26D'); }); - it('should be f2f2f2', () => { + it('should get the next threshold color if value is same as a threshold', () => { const { instance } = setup({ - thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }, { index: 1, value: 59, color: '#f2f2f2' }], + thresholds: [ + { index: 2, value: 75, color: '#6ED0E0' }, + { index: 1, value: 50, color: '#EAB839' }, + { index: 0, value: -Infinity, color: '#7EB26D' }, + ], }); - expect(instance.getFontColor(58)).toEqual('#f2f2f2'); + expect(instance.getFontColor(50)).toEqual('#6ED0E0'); + }); + + it('should get the nearest threshold color', () => { + const { instance } = setup({ + thresholds: [ + { index: 2, value: 75, color: '#6ED0E0' }, + { index: 1, value: 50, color: '#EAB839' }, + { index: 0, value: -Infinity, color: '#7EB26D' }, + ], + }); + + expect(instance.getFontColor(6.5)).toEqual('#EAB839'); }); }); diff --git a/public/app/viz/Gauge.tsx b/public/app/viz/Gauge.tsx index 069a8dbcc3c..75ad799d322 100644 --- a/public/app/viz/Gauge.tsx +++ b/public/app/viz/Gauge.tsx @@ -82,14 +82,14 @@ export class Gauge extends PureComponent { } if (isNaN(value)) { - return '-'; + return value; } return `${prefix} ${formattedValue} ${suffix}`; } - getFontColor(value) { - const { maxValue, thresholds } = this.props; + getFontColor(value: string | number) { + const { thresholds } = this.props; if (thresholds.length === 1) { return thresholds[0].color; @@ -98,12 +98,11 @@ export class Gauge extends PureComponent { const atThreshold = thresholds.filter(threshold => value < threshold.value); if (atThreshold.length > 0) { - return atThreshold[0].color; - } else if (value <= maxValue) { - return BasicGaugeColor.Red; + const nearestThreshold = atThreshold.sort((t1, t2) => t1.value - t2.value)[0]; + return nearestThreshold.color; } - return ''; + return BasicGaugeColor.Red; } draw() { @@ -136,16 +135,16 @@ export class Gauge extends PureComponent { const thresholdMarkersWidth = gaugeWidth / 5; const thresholdLabelFontSize = fontSize / 2.5; - // const formattedThresholds = [ - // { value: minValue, color: BasicGaugeColor.Green }, - // ...thresholds.map((threshold, index) => { - // return { - // value: threshold.value, - // color: index === 0 ? threshold.color : thresholds[index].color, - // }; - // }), - // { value: maxValue, color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor }, - // ]; + const formattedThresholds = [ + { value: minValue, color: thresholds.length === 1 ? thresholds[0].color : BasicGaugeColor.Green }, + ...thresholds.map((threshold, index) => { + return { + value: threshold.value, + color: thresholds[index].color, + }; + }), + { value: maxValue, color: thresholds.length === 1 ? thresholds[0].color : BasicGaugeColor.Red }, + ]; const options = { series: { @@ -163,7 +162,7 @@ export class Gauge extends PureComponent { layout: { margin: 0, thresholdWidth: 0 }, cell: { border: { width: 0 } }, threshold: { - values: thresholds, + values: formattedThresholds, label: { show: showThresholdLabels, margin: thresholdMarkersWidth + 1, From 554d010332cf1aceecbfdad15f6ed54445a4264f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 17 Jan 2019 11:01:49 +0100 Subject: [PATCH 03/27] Preparing move to ui/viz --- packages/grafana-ui/src/types/panel.ts | 7 +++++++ public/app/viz/Gauge.tsx | 20 ++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index 7e4012ad529..340bec9d37b 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -66,3 +66,10 @@ export interface RangeMap extends BaseMap { from: string; to: string; } + +export type Theme = 'dark' | 'light'; + +export enum Themes { + Dark = 'dark', + Light = 'light', +} diff --git a/public/app/viz/Gauge.tsx b/public/app/viz/Gauge.tsx index 75ad799d322..1a611d79783 100644 --- a/public/app/viz/Gauge.tsx +++ b/public/app/viz/Gauge.tsx @@ -1,9 +1,15 @@ import React, { PureComponent } from 'react'; import $ from 'jquery'; -import { BasicGaugeColor, Threshold, TimeSeriesVMs, MappingType, ValueMapping } from '@grafana/ui'; - -import config from '../core/config'; -import kbn from '../core/utils/kbn'; +import { + BasicGaugeColor, + Threshold, + TimeSeriesVMs, + MappingType, + ValueMapping, + getValueFormat, + Theme, + Themes, +} from '@grafana/ui'; export interface Props { decimals: number; @@ -20,6 +26,7 @@ export interface Props { suffix: string; unit: string; width: number; + theme?: Theme; } export class Gauge extends PureComponent { @@ -68,7 +75,7 @@ export class Gauge extends PureComponent { formatValue(value) { const { decimals, valueMappings, prefix, suffix, unit } = this.props; - const formatFunc = kbn.valueFormats[unit]; + const formatFunc = getValueFormat(unit); const formattedValue = formatFunc(value, decimals); if (valueMappings.length > 0) { @@ -116,6 +123,7 @@ export class Gauge extends PureComponent { width, height, stat, + theme, } = this.props; let value: string | number = ''; @@ -127,7 +135,7 @@ export class Gauge extends PureComponent { } const dimension = Math.min(width, height * 1.3); - const backgroundColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)'; + const backgroundColor = theme === Themes.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)'; const fontScale = parseInt('80', 10) / 100; const fontSize = Math.min(dimension / 5, 100) * fontScale; const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1; From 9a01f3e5178a49b625496c4ca9d6eeada588c36d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 17 Jan 2019 11:55:22 +0100 Subject: [PATCH 04/27] Moved Gauge to ui/components --- .../src/components/Gauge}/Gauge.test.tsx | 2 +- .../grafana-ui/src/components/Gauge}/Gauge.tsx | 14 ++++---------- packages/grafana-ui/src/components/index.ts | 1 + public/app/plugins/panel/gauge/GaugePanel.tsx | 5 ++--- .../state => plugins/panel/gauge}/timeSeries.ts | 0 5 files changed, 8 insertions(+), 14 deletions(-) rename {public/app/viz => packages/grafana-ui/src/components/Gauge}/Gauge.test.tsx (97%) rename {public/app/viz => packages/grafana-ui/src/components/Gauge}/Gauge.tsx (95%) rename public/app/{viz/state => plugins/panel/gauge}/timeSeries.ts (100%) diff --git a/public/app/viz/Gauge.test.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx similarity index 97% rename from public/app/viz/Gauge.test.tsx rename to packages/grafana-ui/src/components/Gauge/Gauge.test.tsx index 3fed641c9a2..999f3f581ab 100644 --- a/public/app/viz/Gauge.test.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { TimeSeriesVMs } from '@grafana/ui'; import { Gauge, Props } from './Gauge'; +import { TimeSeriesVMs } from '../../types/series'; jest.mock('jquery', () => ({ plot: jest.fn(), diff --git a/public/app/viz/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx similarity index 95% rename from public/app/viz/Gauge.tsx rename to packages/grafana-ui/src/components/Gauge/Gauge.tsx index 1a611d79783..b658ef4e023 100644 --- a/public/app/viz/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -1,15 +1,9 @@ import React, { PureComponent } from 'react'; import $ from 'jquery'; -import { - BasicGaugeColor, - Threshold, - TimeSeriesVMs, - MappingType, - ValueMapping, - getValueFormat, - Theme, - Themes, -} from '@grafana/ui'; + +import { ValueMapping, Threshold, Theme, MappingType, BasicGaugeColor, Themes } from '../../types/panel'; +import { TimeSeriesVMs } from '../../types/series'; +import { getValueFormat } from '../../utils/valueFormats/valueFormats'; export interface Props { decimals: number; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 88959bd8cb9..584992f8803 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -22,3 +22,4 @@ export { Graph } from './Graph/Graph'; export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid'; export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor'; +export { Gauge } from './Gauge/Gauge'; diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index cfce719b5a6..eb00caf55fb 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -1,8 +1,7 @@ import React, { PureComponent } from 'react'; -import { PanelProps, NullValueMode } from '@grafana/ui'; +import { PanelProps, NullValueMode, Gauge } from '@grafana/ui'; -import { getTimeSeriesVMs } from 'app/viz/state/timeSeries'; -import Gauge from 'app/viz/Gauge'; +import { getTimeSeriesVMs } from './timeSeries'; import { GaugeOptions } from './types'; interface Props extends PanelProps {} diff --git a/public/app/viz/state/timeSeries.ts b/public/app/plugins/panel/gauge/timeSeries.ts similarity index 100% rename from public/app/viz/state/timeSeries.ts rename to public/app/plugins/panel/gauge/timeSeries.ts From a6e2be862c099ead673373f7774189ab6c9e4b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 17 Jan 2019 14:04:39 +0100 Subject: [PATCH 05/27] Added typings and refactored valuemappings code --- .../grafana-ui/src/components/Gauge/Gauge.tsx | 115 +++++++++++++----- packages/grafana-ui/src/types/series.ts | 5 +- .../grafana-ui/src/utils/processTimeSeries.ts | 4 +- public/app/plugins/panel/gauge/timeSeries.ts | 4 +- 4 files changed, 92 insertions(+), 36 deletions(-) diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index b658ef4e023..aefd6ed7882 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -1,10 +1,21 @@ import React, { PureComponent } from 'react'; import $ from 'jquery'; -import { ValueMapping, Threshold, Theme, MappingType, BasicGaugeColor, Themes } from '../../types/panel'; +import { + ValueMapping, + Threshold, + Theme, + MappingType, + BasicGaugeColor, + Themes, + ValueMap, + RangeMap, +} from '../../types/panel'; import { TimeSeriesVMs } from '../../types/series'; import { getValueFormat } from '../../utils/valueFormats/valueFormats'; +type TimeSeriesValue = string | number | null; + export interface Props { decimals: number; height: number; @@ -47,56 +58,100 @@ export class Gauge extends PureComponent { this.draw(); } - formatWithMappings(mappings, value) { - const valueMaps = mappings.filter(m => m.type === MappingType.ValueToText); - const rangeMaps = mappings.filter(m => m.type === MappingType.RangeToText); + addValueToTextMappingText( + allTexts: Array<{ text: string; type: MappingType }>, + valueToTextMapping: ValueMap, + value: TimeSeriesValue + ) { + if (!valueToTextMapping.value) { + return allTexts; + } - const valueMap = valueMaps.map(mapping => { - if (mapping.value && value === mapping.value) { - return mapping.text; - } - })[0]; + const valueAsNumber = parseFloat(value as string); + const valueToTextMappingAsNumber = parseFloat(valueToTextMapping.value as string); - const rangeMap = rangeMaps.map(mapping => { - if (mapping.from && mapping.to && value > mapping.from && value < mapping.to) { - return mapping.text; - } - })[0]; + if (isNaN(valueAsNumber) || isNaN(valueToTextMappingAsNumber)) { + return allTexts; + } - return { rangeMap, valueMap }; + if (valueAsNumber !== valueToTextMappingAsNumber) { + return allTexts; + } + + return allTexts.concat({ text: valueToTextMapping.text, type: MappingType.ValueToText }); } - formatValue(value) { + addRangeToTextMappingText( + allTexts: Array<{ text: string; type: MappingType }>, + rangeToTextMapping: RangeMap, + value: TimeSeriesValue + ) { + if ( + rangeToTextMapping.from && + rangeToTextMapping.to && + value && + value >= rangeToTextMapping.from && + value <= rangeToTextMapping.to + ) { + return allTexts.concat({ text: rangeToTextMapping.text, type: MappingType.RangeToText }); + } + + return allTexts; + } + + getAllMappingTexts(valueMappings: ValueMapping[], value: TimeSeriesValue) { + const allMappingTexts = valueMappings.reduce( + (allTexts, valueMapping) => { + if (valueMapping.type === MappingType.ValueToText) { + allTexts = this.addValueToTextMappingText(allTexts, valueMapping as ValueMap, value); + } else if (valueMapping.type === MappingType.RangeToText) { + allTexts = this.addRangeToTextMappingText(allTexts, valueMapping as RangeMap, value); + } + + return allTexts; + }, + [] as Array<{ text: string; type: MappingType }> + ); + + allMappingTexts.sort((t1, t2) => { + return t1.type - t2.type; + }); + + return allMappingTexts; + } + + formatWithValueMappings(valueMappings: ValueMapping[], value: TimeSeriesValue) { + return this.getAllMappingTexts(valueMappings, value)[0]; + } + + formatValue(value: TimeSeriesValue) { const { decimals, valueMappings, prefix, suffix, unit } = this.props; - const formatFunc = getValueFormat(unit); - const formattedValue = formatFunc(value, decimals); + if (isNaN(value as number)) { + return value; + } if (valueMappings.length > 0) { - const { rangeMap, valueMap } = this.formatWithMappings(valueMappings, formattedValue); - - if (valueMap) { - return `${prefix} ${valueMap} ${suffix}`; - } else if (rangeMap) { - return `${prefix} ${rangeMap} ${suffix}`; + const valueMappedValue = this.formatWithValueMappings(valueMappings, value); + if (valueMappedValue) { + return `${prefix} ${valueMappedValue.text} ${suffix}`; } } - if (isNaN(value)) { - return value; - } + const formatFunc = getValueFormat(unit); + const formattedValue = formatFunc(value as number, decimals); return `${prefix} ${formattedValue} ${suffix}`; } - getFontColor(value: string | number) { + getFontColor(value: TimeSeriesValue) { const { thresholds } = this.props; if (thresholds.length === 1) { return thresholds[0].color; } - const atThreshold = thresholds.filter(threshold => value < threshold.value); + const atThreshold = thresholds.filter(threshold => (value as number) < threshold.value); if (atThreshold.length > 0) { const nearestThreshold = atThreshold.sort((t1, t2) => t1.value - t2.value)[0]; @@ -120,7 +175,7 @@ export class Gauge extends PureComponent { theme, } = this.props; - let value: string | number = ''; + let value: TimeSeriesValue = ''; if (timeSeries[0]) { value = timeSeries[0].stats[stat]; diff --git a/packages/grafana-ui/src/types/series.ts b/packages/grafana-ui/src/types/series.ts index 49662e9872d..5cad1e4a72a 100644 --- a/packages/grafana-ui/src/types/series.ts +++ b/packages/grafana-ui/src/types/series.ts @@ -21,9 +21,12 @@ export interface TimeSeriesVM { color: string; data: TimeSeriesValue[][]; stats: TimeSeriesStats; + allIsNull: boolean; + allIsZero: boolean; } export interface TimeSeriesStats { + [key: string]: number | null; total: number | null; max: number | null; min: number | null; @@ -36,8 +39,6 @@ export interface TimeSeriesStats { range: number | null; timeStep: number; count: number; - allIsNull: boolean; - allIsZero: boolean; } export enum NullValueMode { diff --git a/packages/grafana-ui/src/utils/processTimeSeries.ts b/packages/grafana-ui/src/utils/processTimeSeries.ts index e92aaf0c1a6..7254354a21b 100644 --- a/packages/grafana-ui/src/utils/processTimeSeries.ts +++ b/packages/grafana-ui/src/utils/processTimeSeries.ts @@ -151,6 +151,8 @@ export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: O data: result, label: label, color: colorPalette[colorIndex], + allIsZero, + allIsNull, stats: { total, min, @@ -164,8 +166,6 @@ export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: O range, count, first, - allIsZero, - allIsNull, }, }; }); diff --git a/public/app/plugins/panel/gauge/timeSeries.ts b/public/app/plugins/panel/gauge/timeSeries.ts index 5f27974a33b..18054fe0d5e 100644 --- a/public/app/plugins/panel/gauge/timeSeries.ts +++ b/public/app/plugins/panel/gauge/timeSeries.ts @@ -145,6 +145,8 @@ export function getTimeSeriesVMs({ timeSeries, nullValueMode }: Options): TimeSe data: result, label: label, color: colors[colorIndex], + allIsZero, + allIsNull, stats: { total, min, @@ -158,8 +160,6 @@ export function getTimeSeriesVMs({ timeSeries, nullValueMode }: Options): TimeSe range, count, first, - allIsZero, - allIsNull, }, }; }); From 4f6e87bbbf1ea66dfd780956caaf09c15649c41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 17 Jan 2019 14:48:20 +0100 Subject: [PATCH 06/27] Small refactor of Gauge and tests --- .../src/components/Gauge/Gauge.test.tsx | 78 +++++++++++++++++++ .../grafana-ui/src/components/Gauge/Gauge.tsx | 50 +++++------- 2 files changed, 99 insertions(+), 29 deletions(-) diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx index 999f3f581ab..84f6b921a38 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx @@ -3,6 +3,7 @@ import { shallow } from 'enzyme'; import { Gauge, Props } from './Gauge'; import { TimeSeriesVMs } from '../../types/series'; +import { ValueMapping, MappingType } from '../../types'; jest.mock('jquery', () => ({ plot: jest.fn(), @@ -68,3 +69,80 @@ describe('Get font color', () => { expect(instance.getFontColor(6.5)).toEqual('#EAB839'); }); }); + +describe('Format value with value mappings', () => { + it('should return undefined with no valuemappings', () => { + const valueMappings: ValueMapping[] = []; + const value = 10; + const { instance } = setup({ valueMappings }); + + const result = instance.getFirstFormattedValueMapping(valueMappings, value); + + expect(result).toBeUndefined(); + }); + + it('should return undefined with no matching valuemappings', () => { + const valueMappings: ValueMapping[] = [ + { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, + ]; + const value = 10; + const { instance } = setup({ valueMappings }); + + const result = instance.getFirstFormattedValueMapping(valueMappings, value); + + expect(result).toBeUndefined(); + }); + + it('should return first matching mapping with lowest id', () => { + const valueMappings: ValueMapping[] = [ + { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, + { id: 1, operator: '', text: 'tio', type: MappingType.ValueToText, value: '10' }, + ]; + const value = 10; + const { instance } = setup({ valueMappings }); + + const result = instance.getFirstFormattedValueMapping(valueMappings, value); + + expect(result.text).toEqual('1-20'); + }); + + it('should return rangeToText mapping where value equals to', () => { + const valueMappings: ValueMapping[] = [ + { id: 0, operator: '', text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' }, + { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + ]; + const value = 10; + const { instance } = setup({ valueMappings }); + + const result = instance.getFirstFormattedValueMapping(valueMappings, value); + + expect(result.text).toEqual('1-10'); + }); + + it('should return rangeToText mapping where value equals from', () => { + const valueMappings: ValueMapping[] = [ + { id: 0, operator: '', text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' }, + { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + ]; + const value = 10; + const { instance } = setup({ valueMappings }); + + const result = instance.getFirstFormattedValueMapping(valueMappings, value); + + expect(result.text).toEqual('10-20'); + }); + + it('should return rangeToText mapping where value is between from and to', () => { + const valueMappings: ValueMapping[] = [ + { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, + { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + ]; + const value = 10; + const { instance } = setup({ valueMappings }); + + const result = instance.getFirstFormattedValueMapping(valueMappings, value); + + expect(result.text).toEqual('1-20'); + }); +}); diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index aefd6ed7882..6a219a580c7 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -58,34 +58,26 @@ export class Gauge extends PureComponent { this.draw(); } - addValueToTextMappingText( - allTexts: Array<{ text: string; type: MappingType }>, - valueToTextMapping: ValueMap, - value: TimeSeriesValue - ) { + addValueToTextMappingText(allValueMappings: ValueMapping[], valueToTextMapping: ValueMap, value: TimeSeriesValue) { if (!valueToTextMapping.value) { - return allTexts; + return allValueMappings; } const valueAsNumber = parseFloat(value as string); const valueToTextMappingAsNumber = parseFloat(valueToTextMapping.value as string); if (isNaN(valueAsNumber) || isNaN(valueToTextMappingAsNumber)) { - return allTexts; + return allValueMappings; } if (valueAsNumber !== valueToTextMappingAsNumber) { - return allTexts; + return allValueMappings; } - return allTexts.concat({ text: valueToTextMapping.text, type: MappingType.ValueToText }); + return allValueMappings.concat(valueToTextMapping); } - addRangeToTextMappingText( - allTexts: Array<{ text: string; type: MappingType }>, - rangeToTextMapping: RangeMap, - value: TimeSeriesValue - ) { + addRangeToTextMappingText(allValueMappings: ValueMapping[], rangeToTextMapping: RangeMap, value: TimeSeriesValue) { if ( rangeToTextMapping.from && rangeToTextMapping.to && @@ -93,35 +85,35 @@ export class Gauge extends PureComponent { value >= rangeToTextMapping.from && value <= rangeToTextMapping.to ) { - return allTexts.concat({ text: rangeToTextMapping.text, type: MappingType.RangeToText }); + return allValueMappings.concat(rangeToTextMapping); } - return allTexts; + return allValueMappings; } - getAllMappingTexts(valueMappings: ValueMapping[], value: TimeSeriesValue) { - const allMappingTexts = valueMappings.reduce( - (allTexts, valueMapping) => { + getAllFormattedValueMappings(valueMappings: ValueMapping[], value: TimeSeriesValue) { + const allFormattedValueMappings = valueMappings.reduce( + (allValueMappings, valueMapping) => { if (valueMapping.type === MappingType.ValueToText) { - allTexts = this.addValueToTextMappingText(allTexts, valueMapping as ValueMap, value); + allValueMappings = this.addValueToTextMappingText(allValueMappings, valueMapping as ValueMap, value); } else if (valueMapping.type === MappingType.RangeToText) { - allTexts = this.addRangeToTextMappingText(allTexts, valueMapping as RangeMap, value); + allValueMappings = this.addRangeToTextMappingText(allValueMappings, valueMapping as RangeMap, value); } - return allTexts; + return allValueMappings; }, - [] as Array<{ text: string; type: MappingType }> + [] as ValueMapping[] ); - allMappingTexts.sort((t1, t2) => { - return t1.type - t2.type; + allFormattedValueMappings.sort((t1, t2) => { + return t1.id - t2.id; }); - return allMappingTexts; + return allFormattedValueMappings; } - formatWithValueMappings(valueMappings: ValueMapping[], value: TimeSeriesValue) { - return this.getAllMappingTexts(valueMappings, value)[0]; + getFirstFormattedValueMapping(valueMappings: ValueMapping[], value: TimeSeriesValue) { + return this.getAllFormattedValueMappings(valueMappings, value)[0]; } formatValue(value: TimeSeriesValue) { @@ -132,7 +124,7 @@ export class Gauge extends PureComponent { } if (valueMappings.length > 0) { - const valueMappedValue = this.formatWithValueMappings(valueMappings, value); + const valueMappedValue = this.getFirstFormattedValueMapping(valueMappings, value); if (valueMappedValue) { return `${prefix} ${valueMappedValue.text} ${suffix}`; } From 8ccf212f343cfe77c9184ca7ce4d40a722df6624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 17 Jan 2019 15:14:07 +0100 Subject: [PATCH 07/27] Added tests for formatted value --- .../src/components/Gauge/Gauge.test.tsx | 60 +++++++++++++++++-- .../grafana-ui/src/components/Gauge/Gauge.tsx | 20 ++++--- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx index 84f6b921a38..f8f545694dc 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx @@ -73,7 +73,7 @@ describe('Get font color', () => { describe('Format value with value mappings', () => { it('should return undefined with no valuemappings', () => { const valueMappings: ValueMapping[] = []; - const value = 10; + const value = '10'; const { instance } = setup({ valueMappings }); const result = instance.getFirstFormattedValueMapping(valueMappings, value); @@ -86,7 +86,7 @@ describe('Format value with value mappings', () => { { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, ]; - const value = 10; + const value = '10'; const { instance } = setup({ valueMappings }); const result = instance.getFirstFormattedValueMapping(valueMappings, value); @@ -99,7 +99,7 @@ describe('Format value with value mappings', () => { { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, { id: 1, operator: '', text: 'tio', type: MappingType.ValueToText, value: '10' }, ]; - const value = 10; + const value = '10'; const { instance } = setup({ valueMappings }); const result = instance.getFirstFormattedValueMapping(valueMappings, value); @@ -112,7 +112,7 @@ describe('Format value with value mappings', () => { { id: 0, operator: '', text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' }, { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, ]; - const value = 10; + const value = '10'; const { instance } = setup({ valueMappings }); const result = instance.getFirstFormattedValueMapping(valueMappings, value); @@ -125,7 +125,7 @@ describe('Format value with value mappings', () => { { id: 0, operator: '', text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' }, { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, ]; - const value = 10; + const value = '10'; const { instance } = setup({ valueMappings }); const result = instance.getFirstFormattedValueMapping(valueMappings, value); @@ -138,7 +138,7 @@ describe('Format value with value mappings', () => { { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, ]; - const value = 10; + const value = '10'; const { instance } = setup({ valueMappings }); const result = instance.getFirstFormattedValueMapping(valueMappings, value); @@ -146,3 +146,51 @@ describe('Format value with value mappings', () => { expect(result.text).toEqual('1-20'); }); }); + +describe('Format value', () => { + it('should return if value isNaN', () => { + const valueMappings: ValueMapping[] = []; + const value = 'N/A'; + const { instance } = setup({ valueMappings }); + + const result = instance.formatValue(value); + + expect(result).toEqual('N/A'); + }); + + it('should return formatted value if there are no value mappings', () => { + const valueMappings: ValueMapping[] = []; + const value = '6'; + const { instance } = setup({ valueMappings, decimals: 1 }); + + const result = instance.formatValue(value); + + expect(result).toEqual(' 6.0 '); + }); + + it('should return formatted value if there are no matching value mappings', () => { + const valueMappings: ValueMapping[] = [ + { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, + ]; + const value = '10'; + const { instance } = setup({ valueMappings, decimals: 1 }); + + const result = instance.formatValue(value); + + expect(result).toEqual(' 10.0 '); + }); + + it('should return mapped value if there are matching value mappings', () => { + const valueMappings: ValueMapping[] = [ + { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, + { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + ]; + const value = '11'; + const { instance } = setup({ valueMappings, decimals: 1 }); + + const result = instance.formatValue(value); + + expect(result).toEqual(' 1-20 '); + }); +}); diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 6a219a580c7..c590b1ad9b7 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -78,13 +78,19 @@ export class Gauge extends PureComponent { } addRangeToTextMappingText(allValueMappings: ValueMapping[], rangeToTextMapping: RangeMap, value: TimeSeriesValue) { - if ( - rangeToTextMapping.from && - rangeToTextMapping.to && - value && - value >= rangeToTextMapping.from && - value <= rangeToTextMapping.to - ) { + if (!rangeToTextMapping.from || !rangeToTextMapping.to || !value) { + return allValueMappings; + } + + const valueAsNumber = parseFloat(value as string); + const fromAsNumber = parseFloat(rangeToTextMapping.from as string); + const toAsNumber = parseFloat(rangeToTextMapping.to as string); + + if (isNaN(valueAsNumber) || isNaN(fromAsNumber) || isNaN(toAsNumber)) { + return allValueMappings; + } + + if (valueAsNumber >= fromAsNumber && valueAsNumber <= toAsNumber) { return allValueMappings.concat(rangeToTextMapping); } From 5448b72f7ca1c58dcee38213ccf7453156624e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 18 Jan 2019 06:57:00 +0100 Subject: [PATCH 08/27] Passed the theme to Gauge --- public/app/plugins/panel/gauge/GaugePanel.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index eb00caf55fb..52ee273ef21 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -1,12 +1,17 @@ import React, { PureComponent } from 'react'; -import { PanelProps, NullValueMode, Gauge } from '@grafana/ui'; +import { PanelProps, NullValueMode, Gauge, Themes } from '@grafana/ui'; import { getTimeSeriesVMs } from './timeSeries'; import { GaugeOptions } from './types'; +import { contextSrv } from 'app/core/core'; interface Props extends PanelProps {} export class GaugePanel extends PureComponent { + getTheme() { + return contextSrv.user.lightTheme ? Themes.Light : Themes.Dark; + } + render() { const { timeSeries, width, height, onInterpolate, options } = this.props; @@ -26,6 +31,7 @@ export class GaugePanel extends PureComponent { height={height} prefix={prefix} suffix={suffix} + theme={this.getTheme()} /> ); } From c17ccf2289945792c1a399bf56c590b169c82baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 18 Jan 2019 07:10:00 +0100 Subject: [PATCH 09/27] Make sure we do not change -Infinity --- .../src/components/ThresholdsEditor/ThresholdsEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx index bd4f83f4dba..590aca5c7a1 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx @@ -105,7 +105,7 @@ export class ThresholdsEditor extends PureComponent { const value = isNaN(parsedValue) ? null : parsedValue; const newThresholds = thresholds.map(t => { - if (t === threshold) { + if (t === threshold && t.index !== 0) { t = { ...t, value: value as number }; } From 4cc0be2568148b8d480b626ecd271cf1530be3a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 18 Jan 2019 09:54:25 +0100 Subject: [PATCH 10/27] Redid logic for fontcolor and thresholds in Gauge and added tests --- .../src/components/Gauge/Gauge.test.tsx | 36 +++++++++++++-- .../grafana-ui/src/components/Gauge/Gauge.tsx | 46 ++++++++++++------- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx index f8f545694dc..b3396841d4d 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx @@ -45,7 +45,7 @@ describe('Get font color', () => { expect(instance.getFontColor(49)).toEqual('#7EB26D'); }); - it('should get the next threshold color if value is same as a threshold', () => { + it('should get the threshold color if value is same as a threshold', () => { const { instance } = setup({ thresholds: [ { index: 2, value: 75, color: '#6ED0E0' }, @@ -54,10 +54,10 @@ describe('Get font color', () => { ], }); - expect(instance.getFontColor(50)).toEqual('#6ED0E0'); + expect(instance.getFontColor(50)).toEqual('#EAB839'); }); - it('should get the nearest threshold color', () => { + it('should get the nearest threshold color between thresholds', () => { const { instance } = setup({ thresholds: [ { index: 2, value: 75, color: '#6ED0E0' }, @@ -66,7 +66,35 @@ describe('Get font color', () => { ], }); - expect(instance.getFontColor(6.5)).toEqual('#EAB839'); + expect(instance.getFontColor(55)).toEqual('#EAB839'); + }); +}); + +describe('Get thresholds formatted', () => { + it('should return first thresholds color for min and max', () => { + const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] }); + + expect(instance.getFormattedThresholds()).toEqual([ + { value: 0, color: '#7EB26D' }, + { value: 100, color: '#7EB26D' }, + ]); + }); + + it('should get the correct formatted values when thresholds are added', () => { + const { instance } = setup({ + thresholds: [ + { index: 2, value: 75, color: '#6ED0E0' }, + { index: 1, value: 50, color: '#EAB839' }, + { index: 0, value: -Infinity, color: '#7EB26D' }, + ], + }); + + expect(instance.getFormattedThresholds()).toEqual([ + { value: 0, color: '#7EB26D' }, + { value: 50, color: '#7EB26D' }, + { value: 75, color: '#EAB839' }, + { value: 100, color: '#6ED0E0' }, + ]); }); }); diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index c590b1ad9b7..8013387812a 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -149,16 +149,42 @@ export class Gauge extends PureComponent { return thresholds[0].color; } - const atThreshold = thresholds.filter(threshold => (value as number) < threshold.value); + const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0]; + if (atThreshold) { + return atThreshold.color; + } - if (atThreshold.length > 0) { - const nearestThreshold = atThreshold.sort((t1, t2) => t1.value - t2.value)[0]; + const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value); + + if (belowThreshold.length > 0) { + const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0]; return nearestThreshold.color; } return BasicGaugeColor.Red; } + getFormattedThresholds() { + const { maxValue, minValue, thresholds } = this.props; + + const thresholdsSortedByIndex = [...thresholds].sort((t1, t2) => t1.index - t2.index); + const lastThreshold = thresholdsSortedByIndex[thresholdsSortedByIndex.length - 1]; + + const formattedThresholds = [ + ...thresholdsSortedByIndex.map(threshold => { + if (threshold.index === 0) { + return { value: minValue, color: threshold.color }; + } + + const previousThreshold = thresholdsSortedByIndex[threshold.index - 1]; + return { value: threshold.value, color: previousThreshold.color }; + }), + { value: maxValue, color: lastThreshold.color }, + ]; + + return formattedThresholds; + } + draw() { const { maxValue, @@ -166,7 +192,6 @@ export class Gauge extends PureComponent { timeSeries, showThresholdLabels, showThresholdMarkers, - thresholds, width, height, stat, @@ -190,17 +215,6 @@ export class Gauge extends PureComponent { const thresholdMarkersWidth = gaugeWidth / 5; const thresholdLabelFontSize = fontSize / 2.5; - const formattedThresholds = [ - { value: minValue, color: thresholds.length === 1 ? thresholds[0].color : BasicGaugeColor.Green }, - ...thresholds.map((threshold, index) => { - return { - value: threshold.value, - color: thresholds[index].color, - }; - }), - { value: maxValue, color: thresholds.length === 1 ? thresholds[0].color : BasicGaugeColor.Red }, - ]; - const options = { series: { gauges: { @@ -217,7 +231,7 @@ export class Gauge extends PureComponent { layout: { margin: 0, thresholdWidth: 0 }, cell: { border: { width: 0 } }, threshold: { - values: formattedThresholds, + values: this.getFormattedThresholds(), label: { show: showThresholdLabels, margin: thresholdMarkersWidth + 1, From f2498645d071afb210b5ccbda97efd0b9057b9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 11:20:55 +0100 Subject: [PATCH 11/27] Fixed issue with explore angular query editor support introduced by recent angular query editor changes --- .../features/dashboard/panel_editor/QueryEditorRow.tsx | 2 +- public/app/features/explore/QueryEditor.tsx | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index 1289ad3bac7..a7638e82a23 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx @@ -51,7 +51,7 @@ export class QueryEditorRow extends PureComponent { target: query, panel: panel, refresh: () => panel.refresh(), - render: () => panel.render, + render: () => panel.render(), events: panel.events, }; } diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 3414d93a8ad..7bca9cab99b 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -3,7 +3,6 @@ import React, { PureComponent } from 'react'; // Services import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; -import { getIntervals } from 'app/core/utils/explore'; import { getTimeSrv } from 'app/features/dashboard/time_srv'; // Types @@ -37,8 +36,9 @@ export default class QueryEditor extends PureComponent { const template = ' '; const target = { datasource: datasource.name, ...initialQuery }; const scopeProps = { - target, ctrl: { + datasource, + target, refresh: () => { this.props.onQueryChange(target, false); this.props.onExecuteQuery(); @@ -48,11 +48,7 @@ export default class QueryEditor extends PureComponent { datasource, targets: [target], }, - dashboard: { - getNextQueryLetter: x => '', - }, - hideEditorRowActions: true, - ...getIntervals(range, (datasource || {}).interval, null), // Possible to get resolution? + dashboard: {}, }, }; From f16101101d2035f6e5ce1f06f4e40efe8748fae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 11:58:29 +0100 Subject: [PATCH 12/27] Minor refactoring and name changes --- .../grafana-ui/src/components/Gauge/Gauge.tsx | 9 +- packages/grafana-ui/src/types/panel.ts | 4 +- .../grafana-ui/src/utils/processTimeSeries.ts | 9 +- public/app/core/services/context_srv.ts | 5 + public/app/plugins/panel/gauge/GaugePanel.tsx | 20 ++- public/app/plugins/panel/gauge/timeSeries.ts | 168 ------------------ .../app/plugins/panel/graph2/GraphPanel.tsx | 2 - 7 files changed, 29 insertions(+), 188 deletions(-) delete mode 100644 public/app/plugins/panel/gauge/timeSeries.ts diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 8013387812a..63d875e9cd5 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -4,10 +4,10 @@ import $ from 'jquery'; import { ValueMapping, Threshold, - Theme, + ThemeName, MappingType, BasicGaugeColor, - Themes, + ThemeNames, ValueMap, RangeMap, } from '../../types/panel'; @@ -31,7 +31,7 @@ export interface Props { suffix: string; unit: string; width: number; - theme?: Theme; + theme?: ThemeName; } export class Gauge extends PureComponent { @@ -48,6 +48,7 @@ export class Gauge extends PureComponent { thresholds: [], unit: 'none', stat: 'avg', + theme: ThemeNames.Dark, }; componentDidMount() { @@ -207,7 +208,7 @@ export class Gauge extends PureComponent { } const dimension = Math.min(width, height * 1.3); - const backgroundColor = theme === Themes.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)'; + const backgroundColor = theme === ThemeNames.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)'; const fontScale = parseInt('80', 10) / 100; const fontSize = Math.min(dimension / 5, 100) * fontScale; const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1; diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index 340bec9d37b..881bf920c27 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -67,9 +67,9 @@ export interface RangeMap extends BaseMap { to: string; } -export type Theme = 'dark' | 'light'; +export type ThemeName = 'dark' | 'light'; -export enum Themes { +export enum ThemeNames { Dark = 'dark', Light = 'light', } diff --git a/packages/grafana-ui/src/utils/processTimeSeries.ts b/packages/grafana-ui/src/utils/processTimeSeries.ts index 7254354a21b..7b0c8e55239 100644 --- a/packages/grafana-ui/src/utils/processTimeSeries.ts +++ b/packages/grafana-ui/src/utils/processTimeSeries.ts @@ -1,18 +1,19 @@ // Libraries import _ from 'lodash'; +import { colors } from './colors'; + // Types import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types'; interface Options { timeSeries: TimeSeries[]; nullValueMode: NullValueMode; - colorPalette: string[]; } -export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: Options): TimeSeriesVMs { +export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeSeriesVMs { const vmSeries = timeSeries.map((item, index) => { - const colorIndex = index % colorPalette.length; + const colorIndex = index % colors.length; const label = item.target; const result = []; @@ -150,7 +151,7 @@ export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: O return { data: result, label: label, - color: colorPalette[colorIndex], + color: colors[colorIndex], allIsZero, allIsNull, stats: { diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index c4134598175..5353fb507cc 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -2,6 +2,7 @@ import config from 'app/core/config'; import _ from 'lodash'; import coreModule from 'app/core/core_module'; import store from 'app/core/store'; +import { ThemeNames, ThemeName } from '@grafana/ui'; export class User { isGrafanaAdmin: any; @@ -59,6 +60,10 @@ export class ContextSrv { this.sidemenu = !this.sidemenu; store.set('grafana.sidemenu', this.sidemenu); } + + getTheme(): ThemeName { + return this.user.lightTheme ? ThemeNames.Light : ThemeNames.Dark; + } } const contextSrv = new ContextSrv(); diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 52ee273ef21..8b62171f31b 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -1,16 +1,20 @@ +// Libraries import React, { PureComponent } from 'react'; -import { PanelProps, NullValueMode, Gauge, Themes } from '@grafana/ui'; -import { getTimeSeriesVMs } from './timeSeries'; -import { GaugeOptions } from './types'; +// Services & Utils import { contextSrv } from 'app/core/core'; +import { processTimeSeries } from '@grafana/ui'; + +// Components +import { Gauge } from '@grafana/ui'; + +// Types +import { GaugeOptions } from './types'; +import { PanelProps, NullValueMode } from '@grafana/ui/src/types'; interface Props extends PanelProps {} export class GaugePanel extends PureComponent { - getTheme() { - return contextSrv.user.lightTheme ? Themes.Light : Themes.Dark; - } render() { const { timeSeries, width, height, onInterpolate, options } = this.props; @@ -18,7 +22,7 @@ export class GaugePanel extends PureComponent { const prefix = onInterpolate(options.prefix); const suffix = onInterpolate(options.suffix); - const vmSeries = getTimeSeriesVMs({ + const vmSeries = processTimeSeries({ timeSeries: timeSeries, nullValueMode: NullValueMode.Ignore, }); @@ -31,7 +35,7 @@ export class GaugePanel extends PureComponent { height={height} prefix={prefix} suffix={suffix} - theme={this.getTheme()} + theme={contextSrv.getTheme()} /> ); } diff --git a/public/app/plugins/panel/gauge/timeSeries.ts b/public/app/plugins/panel/gauge/timeSeries.ts deleted file mode 100644 index 18054fe0d5e..00000000000 --- a/public/app/plugins/panel/gauge/timeSeries.ts +++ /dev/null @@ -1,168 +0,0 @@ -// Libraries -import _ from 'lodash'; - -// Utils -import { colors } from '@grafana/ui'; - -// Types -import { TimeSeries, TimeSeriesVMs, NullValueMode } from '@grafana/ui'; - -interface Options { - timeSeries: TimeSeries[]; - nullValueMode: NullValueMode; -} - -export function getTimeSeriesVMs({ timeSeries, nullValueMode }: Options): TimeSeriesVMs { - const vmSeries = timeSeries.map((item, index) => { - const colorIndex = index % colors.length; - const label = item.target; - const result = []; - - // stat defaults - let total = 0; - let max = -Number.MAX_VALUE; - let min = Number.MAX_VALUE; - let logmin = Number.MAX_VALUE; - let avg = null; - let current = null; - let first = null; - let delta = 0; - let diff = null; - let range = null; - let timeStep = Number.MAX_VALUE; - let allIsNull = true; - let allIsZero = true; - - const ignoreNulls = nullValueMode === NullValueMode.Ignore; - const nullAsZero = nullValueMode === NullValueMode.AsZero; - - let currentTime; - let currentValue; - let nonNulls = 0; - let previousTime; - let previousValue = 0; - let previousDeltaUp = true; - - for (let i = 0; i < item.datapoints.length; i++) { - currentValue = item.datapoints[i][0]; - currentTime = item.datapoints[i][1]; - - // Due to missing values we could have different timeStep all along the series - // so we have to find the minimum one (could occur with aggregators such as ZimSum) - if (previousTime !== undefined) { - const currentStep = currentTime - previousTime; - if (currentStep < timeStep) { - timeStep = currentStep; - } - } - - previousTime = currentTime; - - if (currentValue === null) { - if (ignoreNulls) { - continue; - } - if (nullAsZero) { - currentValue = 0; - } - } - - if (currentValue !== null) { - if (_.isNumber(currentValue)) { - total += currentValue; - allIsNull = false; - nonNulls++; - } - - if (currentValue > max) { - max = currentValue; - } - - if (currentValue < min) { - min = currentValue; - } - - if (first === null) { - first = currentValue; - } else { - if (previousValue > currentValue) { - // counter reset - previousDeltaUp = false; - if (i === item.datapoints.length - 1) { - // reset on last - delta += currentValue; - } - } else { - if (previousDeltaUp) { - delta += currentValue - previousValue; // normal increment - } else { - delta += currentValue; // account for counter reset - } - previousDeltaUp = true; - } - } - previousValue = currentValue; - - if (currentValue < logmin && currentValue > 0) { - logmin = currentValue; - } - - if (currentValue !== 0) { - allIsZero = false; - } - } - - result.push([currentTime, currentValue]); - } - - if (max === -Number.MAX_VALUE) { - max = null; - } - - if (min === Number.MAX_VALUE) { - min = null; - } - - if (result.length && !allIsNull) { - avg = total / nonNulls; - current = result[result.length - 1][1]; - if (current === null && result.length > 1) { - current = result[result.length - 2][1]; - } - } - - if (max !== null && min !== null) { - range = max - min; - } - - if (current !== null && first !== null) { - diff = current - first; - } - - const count = result.length; - - return { - data: result, - label: label, - color: colors[colorIndex], - allIsZero, - allIsNull, - stats: { - total, - min, - max, - current, - logmin, - avg, - diff, - delta, - timeStep, - range, - count, - first, - }, - }; - }); - - return vmSeries; -} diff --git a/public/app/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index 28c17dbad2c..2fef35b4f5f 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -1,7 +1,6 @@ // Libraries import _ from 'lodash'; import React, { PureComponent } from 'react'; -import { colors } from '@grafana/ui'; // Utils import { processTimeSeries } from '@grafana/ui/src/utils'; @@ -23,7 +22,6 @@ export class GraphPanel extends PureComponent { const vmSeries = processTimeSeries({ timeSeries: timeSeries, nullValueMode: NullValueMode.Ignore, - colorPalette: colors, }); return ( From 11944adc9cf509c4e8ba4314a4a6d032db0bf074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 18 Jan 2019 12:03:16 +0100 Subject: [PATCH 13/27] fix: Hack for getting the same height in splitted view, view could use refactor IMHO #14853 --- public/app/features/explore/Explore.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index d77448c10d7..b6f57a76004 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -242,11 +242,14 @@ export class Explore extends React.PureComponent { ) : ( -
- -
+ <> +
+
+ +
+ )} {!datasourceMissing ? (
@@ -274,7 +277,11 @@ export class Explore extends React.PureComponent {
From 3a827fc2f128a366a30017728055af7e6f93fa47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 13:12:32 +0100 Subject: [PATCH 14/27] Added test case dashboard --- devenv/dev-dashboards/panel_tests_gauge.json | 1250 +++++++++++++++++ .../grafana-ui/src/utils/processTimeSeries.ts | 4 +- public/app/features/dashboard/panel_model.ts | 1 - public/app/plugins/panel/gauge/GaugePanel.tsx | 2 +- 4 files changed, 1253 insertions(+), 4 deletions(-) create mode 100644 devenv/dev-dashboards/panel_tests_gauge.json diff --git a/devenv/dev-dashboards/panel_tests_gauge.json b/devenv/dev-dashboards/panel_tests_gauge.json new file mode 100644 index 00000000000..c6e81ececc8 --- /dev/null +++ b/devenv/dev-dashboards/panel_tests_gauge.json @@ -0,0 +1,1250 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "iteration": 1547810606599, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 11, + "panels": [], + "title": "Value options tests", + "type": "row" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 1 + }, + "id": 2, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "2", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "ms", + "valueMappings": [] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Average, 2 decimals, ms unit", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 8, + "w": 6, + "x": 5, + "y": 1 + }, + "id": 5, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "max", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "ms", + "valueMappings": [] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Max (90 ms), no decimals", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 8, + "w": 5, + "x": 11, + "y": 1 + }, + "id": 6, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "p", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "s", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Current (10 ms), no unit, prefix (p), suffix (s)", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 4, + "w": 3, + "x": 16, + "y": 1 + }, + "id": 16, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 4, + "w": 5, + "x": 19, + "y": 1 + }, + "id": 18, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10,91" + } + ], + "timeFrom": "1h", + "timeShift": null, + "title": "", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 4, + "w": 3, + "x": 16, + "y": 5 + }, + "id": 17, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 4, + "w": 5, + "x": 19, + "y": 5 + }, + "id": 19, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10,81" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "", + "type": "gauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 15, + "panels": [], + "title": "Value Mappings", + "type": "row" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 8, + "w": 4, + "x": 0, + "y": 10 + }, + "id": 12, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [ + { + "from": "", + "id": 1, + "operator": "", + "text": "TEN", + "to": "", + "type": 1, + "value": "10" + } + ] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "value mapping 10 -> TEN", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "description": "should read N/A", + "gridPos": { + "h": 8, + "w": 4, + "x": 4, + "y": 10 + }, + "id": 13, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [ + { + "from": "", + "id": 1, + "operator": "", + "text": "N/A", + "to": "", + "type": 1, + "value": "null" + } + ] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10,null,null,null,null" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "value mapping null -> N/A", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "description": "should read N/A", + "gridPos": { + "h": 8, + "w": 6, + "x": 8, + "y": 10 + }, + "id": 20, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [ + { + "from": "0", + "id": 1, + "operator": "", + "text": "OK", + "to": "10", + "type": 2, + "value": "null" + } + ] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10,null,null,null,null,10" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "value mapping range, 0-10 -> OK, value 10", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "description": "should read N/A", + "gridPos": { + "h": 8, + "w": 6, + "x": 14, + "y": 10 + }, + "id": 21, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "current", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "none", + "valueMappings": [ + { + "from": "0", + "id": 1, + "operator": "", + "text": "OK", + "to": "90", + "type": 2, + "value": "null" + }, + { + "from": "90", + "id": 2, + "operator": "", + "text": "BAD", + "to": "100", + "type": 2, + "value": "" + } + ] + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,10,null,null,null,null,10,95" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "value mapping range, 90-100 -> BAD, value 90", + "type": "gauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 9, + "panels": [], + "title": "Templating & Repeat", + "type": "row" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 19 + }, + "id": 7, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "2", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "$Servers", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "ms", + "valueMappings": [] + }, + "repeat": "Servers", + "repeatDirection": "h", + "scopedVars": { + "Servers": { + "selected": false, + "text": "server1", + "value": "server1" + } + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "repeat $Servers", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 19 + }, + "id": 22, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "2", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "$Servers", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "ms", + "valueMappings": [] + }, + "repeat": null, + "repeatDirection": "h", + "repeatIteration": 1547810606599, + "repeatPanelId": 7, + "scopedVars": { + "Servers": { + "selected": false, + "text": "server2", + "value": "server2" + } + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "repeat $Servers", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 19 + }, + "id": 23, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "2", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "$Servers", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "ms", + "valueMappings": [] + }, + "repeat": null, + "repeatDirection": "h", + "repeatIteration": 1547810606599, + "repeatPanelId": 7, + "scopedVars": { + "Servers": { + "selected": false, + "text": "server3", + "value": "server3" + } + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "repeat $Servers", + "type": "gauge" + }, + { + "datasource": "gdev-testdata", + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 19 + }, + "id": 24, + "links": [], + "nullPointMode": "null", + "options-gauge": { + "baseColor": "#299c46", + "decimals": "2", + "maxValue": 100, + "minValue": 0, + "options": { + "baseColor": "#299c46", + "decimals": 0, + "maxValue": 100, + "minValue": 0, + "prefix": "", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [], + "unit": "none", + "valueMappings": [] + }, + "prefix": "$Servers", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "stat": "avg", + "suffix": "", + "thresholds": [ + { + "color": "#e24d42", + "index": 2, + "value": 90 + }, + { + "color": "#ef843c", + "index": 1, + "value": 75 + }, + { + "color": "#7EB26D", + "index": 0, + "value": null + } + ], + "unit": "ms", + "valueMappings": [] + }, + "repeat": null, + "repeatDirection": "h", + "repeatIteration": 1547810606599, + "repeatPanelId": 7, + "scopedVars": { + "Servers": { + "selected": false, + "text": "server4", + "value": "server4" + } + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,90,30,5,0" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "repeat $Servers", + "type": "gauge" + } + ], + "refresh": false, + "schemaVersion": 17, + "style": "dark", + "tags": [ + "gdev", + "panel-tests" + ], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "tags": [], + "text": "All", + "value": [ + "$__all" + ] + }, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "Servers", + "options": [ + { + "selected": true, + "text": "All", + "value": "$__all" + }, + { + "selected": false, + "text": "server1", + "value": "server1" + }, + { + "selected": false, + "text": "server2", + "value": "server2" + }, + { + "selected": false, + "text": "server3", + "value": "server3" + }, + { + "selected": false, + "text": "server4", + "value": "server4" + } + ], + "query": "server1,server2,server3,server4", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Panel Tests - Gauge", + "uid": "_5rDmaQiz", + "version": 5 +} diff --git a/packages/grafana-ui/src/utils/processTimeSeries.ts b/packages/grafana-ui/src/utils/processTimeSeries.ts index 7b0c8e55239..f5389f1b2bd 100644 --- a/packages/grafana-ui/src/utils/processTimeSeries.ts +++ b/packages/grafana-ui/src/utils/processTimeSeries.ts @@ -50,8 +50,8 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS continue; } - if (typeof currentValue !== 'number') { - continue; + if (currentValue !== null && typeof currentValue !== 'number') { + throw {message: 'Time series contains non number values'}; } // Due to missing values we could have different timeStep all along the series diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index 2fec8e379dd..f60b207e015 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -52,7 +52,6 @@ const mustKeepProps: { [str: string]: boolean } = { hasRefreshed: true, events: true, cacheTimeout: true, - nullPointMode: true, cachedPluginOptions: true, transparent: true, }; diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 8b62171f31b..cd92f697ced 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -24,7 +24,7 @@ export class GaugePanel extends PureComponent { const vmSeries = processTimeSeries({ timeSeries: timeSeries, - nullValueMode: NullValueMode.Ignore, + nullValueMode: NullValueMode.Null, }); return ( From 46ff9dda71048a4847a78ef99a04c87b10524273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 13:24:26 +0100 Subject: [PATCH 15/27] Fixed scrollbar issue where it jumped to the top --- .../src/components/CustomScrollbar/CustomScrollbar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx index c3fb3f0f0ab..eb50944ad35 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx +++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx @@ -25,7 +25,6 @@ export class CustomScrollbar extends PureComponent { autoHideDuration: 200, autoMaxHeight: '100%', hideTracksWhenNotNeeded: false, - scrollTop: 0, setScrollTop: () => {}, autoHeightMin: '0' }; From 69e3dc2d4f0c278597be0dd90339b5e5bbb69a56 Mon Sep 17 00:00:00 2001 From: SamuelToh Date: Fri, 18 Jan 2019 22:46:43 +1000 Subject: [PATCH 16/27] 14947: fixed incorrect flag --- docs/sources/auth/gitlab.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/auth/gitlab.md b/docs/sources/auth/gitlab.md index 541aed3fd1f..b6028b0a2a7 100644 --- a/docs/sources/auth/gitlab.md +++ b/docs/sources/auth/gitlab.md @@ -47,7 +47,7 @@ authentication: ```bash [auth.gitlab] -enabled = false +enabled = true allow_sign_up = false client_id = GITLAB_APPLICATION_ID client_secret = GITLAB_SECRET From 5f4e4a813debd97331b17c06052f9741088aea34 Mon Sep 17 00:00:00 2001 From: flopp999 <21694965+flopp999@users.noreply.github.com> Date: Fri, 18 Jan 2019 14:06:15 +0100 Subject: [PATCH 17/27] change enabled to true if it is false it will not work --- docs/sources/auth/gitlab.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/auth/gitlab.md b/docs/sources/auth/gitlab.md index 541aed3fd1f..b6028b0a2a7 100644 --- a/docs/sources/auth/gitlab.md +++ b/docs/sources/auth/gitlab.md @@ -47,7 +47,7 @@ authentication: ```bash [auth.gitlab] -enabled = false +enabled = true allow_sign_up = false client_id = GITLAB_APPLICATION_ID client_secret = GITLAB_SECRET From 521c8f34f7ba8f3d503746f7faba2f49f8205d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 15:29:58 +0100 Subject: [PATCH 18/27] Moved add query button to the right --- .../dashboard/panel_editor/EditorTabBody.tsx | 13 +++++-------- .../dashboard/panel_editor/QueriesTab.tsx | 17 ++++++++--------- public/sass/components/_toolbar.scss | 6 ++++++ 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/public/app/features/dashboard/panel_editor/EditorTabBody.tsx b/public/app/features/dashboard/panel_editor/EditorTabBody.tsx index 59cdf6c1215..0413cae8a7b 100644 --- a/public/app/features/dashboard/panel_editor/EditorTabBody.tsx +++ b/public/app/features/dashboard/panel_editor/EditorTabBody.tsx @@ -111,14 +111,11 @@ export class EditorTabBody extends PureComponent { return ( <>
-
{heading}
- {renderToolbar && renderToolbar()} - {toolbarItems.length > 0 && ( - <> -
- {toolbarItems.map(item => this.renderButton(item))} - - )} +
+
{heading}
+ {renderToolbar && renderToolbar()} +
+ {toolbarItems.map(item => this.renderButton(item))}
diff --git a/public/app/features/dashboard/panel_editor/QueriesTab.tsx b/public/app/features/dashboard/panel_editor/QueriesTab.tsx index 5e90187d171..ca06098debd 100644 --- a/public/app/features/dashboard/panel_editor/QueriesTab.tsx +++ b/public/app/features/dashboard/panel_editor/QueriesTab.tsx @@ -18,7 +18,7 @@ import config from 'app/core/config'; // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; -import { DataQuery, DataSourceSelectItem } from '@grafana/ui/src/types'; +import { DataQuery, DataSourceSelectItem } from '@grafana/ui/src/types'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; interface Props { @@ -133,14 +133,13 @@ export class QueriesTab extends PureComponent { return ( <> -
- {!isAddingMixed && ( - - )} - {isAddingMixed && this.renderMixedPicker()} -
+
+ {!isAddingMixed && ( + + )} + {isAddingMixed && this.renderMixedPicker()} ); }; diff --git a/public/sass/components/_toolbar.scss b/public/sass/components/_toolbar.scss index 5f557f89cad..14db85f7e65 100644 --- a/public/sass/components/_toolbar.scss +++ b/public/sass/components/_toolbar.scss @@ -16,6 +16,12 @@ padding-right: 20px; } +.toolbar__left { + display: flex; + flex-grow: 1; + align-items: center; +} + .toolbar__main { padding: 0 $input-padding-x; font-size: $font-size-md; From 98fabfae3e00f9876142ccde77313961494cb374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 17:19:35 +0100 Subject: [PATCH 19/27] wip: typings --- packages/grafana-ui/src/types/datasource.ts | 6 +- packages/grafana-ui/src/types/plugin.ts | 21 +++-- .../app/features/.all.ts@neomake_22624_74.ts | 14 +++ .../prometheus/components/PromQueryField.tsx | 8 +- .../datasource/prometheus/datasource.ts | 94 ++++++++++--------- .../plugins/datasource/prometheus/types.ts | 6 ++ .../datasource/testdata/QueryEditor.tsx | 11 +-- .../plugins/datasource/testdata/datasource.ts | 9 +- .../app/plugins/datasource/testdata/module.ts | 8 +- .../app/plugins/datasource/testdata/types.ts | 11 +++ 10 files changed, 112 insertions(+), 76 deletions(-) create mode 100644 public/app/features/.all.ts@neomake_22624_74.ts create mode 100644 public/app/plugins/datasource/prometheus/types.ts create mode 100644 public/app/plugins/datasource/testdata/types.ts diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index f47e2473a85..cb6115486b1 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -8,14 +8,14 @@ export interface DataQueryResponse { export interface DataQuery { refId: string; - [key: string]: any; + hide?: boolean; } -export interface DataQueryOptions { +export interface DataQueryOptions { timezone: string; range: TimeRange; rangeRaw: RawTimeRange; - targets: DataQuery[]; + targets: TQuery[]; panelId: number; dashboardId: number; cacheTimeout?: string; diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index 30a958420de..8ded328b2da 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -2,10 +2,11 @@ import { ComponentClass } from 'react'; import { PanelProps, PanelOptionsProps } from './panel'; import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource'; -export interface DataSourceApi { - name: string; - meta: PluginMeta; - pluginExports: PluginExports; +export interface DataSourceApi { + // set externally by grafana + name?: string; + meta?: PluginMeta; + pluginExports?: PluginExports; /** * min interval range @@ -15,7 +16,7 @@ export interface DataSourceApi { /** * Imports queries from a different datasource */ - importQueries?(queries: DataQuery[], originMeta: PluginMeta): Promise; + importQueries?(queries: TQuery[], originMeta: PluginMeta): Promise; /** * Initializes a datasource after instantiation @@ -25,7 +26,7 @@ export interface DataSourceApi { /** * Main metrics / data query action */ - query(options: DataQueryOptions): Promise; + query(options: DataQueryOptions): Promise; /** * Test & verify datasource settings & connection details @@ -35,12 +36,12 @@ export interface DataSourceApi { /** * Get hints for query improvements */ - getQueryHints(query: DataQuery, results: any[], ...rest: any): QueryHint[]; + getQueryHints?(query: TQuery, results: any[], ...rest: any): QueryHint[]; } -export interface QueryEditorProps { - datasource: DataSourceApi; - query: DataQuery; +export interface QueryEditorProps { + datasource: DSType; + query: TQuery; onExecuteQuery?: () => void; onQueryChange?: (value: DataQuery) => void; } diff --git a/public/app/features/.all.ts@neomake_22624_74.ts b/public/app/features/.all.ts@neomake_22624_74.ts new file mode 100644 index 00000000000..99de12bf215 --- /dev/null +++ b/public/app/features/.all.ts@neomake_22624_74.ts @@ -0,0 +1,14 @@ +import './annotations/all'; +import './templating/all'; +import './plugins/all'; +import './dashboard/all'; +import './playlist/all'; +import './panel/all'; +import './org/all'; +import './admin'; +import './alerting/NotificationsEditCtrl'; +import './alerting/NotificationsListCtrl'; +import './manage-dashboards'; +import './teams/CreateTeamCtrl'; +import './profile/all'; +import './datasources/settings/dsHttpSettings'; diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index 391d39836ca..6fd450394a3 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -11,7 +11,7 @@ import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/ import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; -import { DataQuery } from '@grafana/ui/src/types'; +import { PromQuery } from '../types'; const HISTOGRAM_GROUP = '__histograms__'; const METRIC_MARK = 'metric'; @@ -88,13 +88,13 @@ interface CascaderOption { interface PromQueryFieldProps { datasource: any; error?: string | JSX.Element; - initialQuery: DataQuery; + initialQuery: PromQuery; hint?: any; history?: any[]; metricsByPrefix?: CascaderOption[]; onClickHintFix?: (action: any) => void; onPressEnter?: () => void; - onQueryChange?: (value: DataQuery, override?: boolean) => void; + onQueryChange?: (value: PromQuery, override?: boolean) => void; } interface PromQueryFieldState { @@ -166,7 +166,7 @@ class PromQueryField extends React.PureComponent - group.rules.filter(rule => rule.type === 'recording').reduce( - (acc, rule) => ({ - ...acc, - [rule.name]: rule.query, - }), - mapping - ), - {} - ); -} - -export function prometheusRegularEscape(value) { - if (typeof value === 'string') { - return value.replace(/'/g, "\\\\'"); - } - return value; -} - -export function prometheusSpecialRegexEscape(value) { - if (typeof value === 'string') { - return prometheusRegularEscape(value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]+?.()]/g, '\\\\$&')); - } - return value; -} - -export class PrometheusDatasource { +export class PrometheusDatasource implements DataSourceApi { type: string; editorSrc: string; name: string; @@ -149,7 +116,7 @@ export class PrometheusDatasource { return this.templateSrv.variableExists(target.expr); } - query(options) { + query(options: DataQueryOptions) { const start = this.getPrometheusTime(options.range.from, false); const end = this.getPrometheusTime(options.range.to, true); @@ -423,7 +390,7 @@ export class PrometheusDatasource { }); } - getExploreState(queries: DataQuery[]): Partial { + getExploreState(queries: PromQuery[]): Partial { let state: Partial = { datasource: this.name }; if (queries && queries.length > 0) { const expandedQueries = queries.map(query => ({ @@ -438,7 +405,7 @@ export class PrometheusDatasource { return state; } - getQueryHints(query: DataQuery, result: any[]) { + getQueryHints(query: PromQuery, result: any[]) { return getQueryHints(query.expr || '', result, this); } @@ -457,7 +424,7 @@ export class PrometheusDatasource { }); } - modifyQuery(query: DataQuery, action: any): DataQuery { + modifyQuery(query: PromQuery, action: any): PromQuery { let expression = query.expr || ''; switch (action.type) { case 'ADD_FILTER': { @@ -507,3 +474,40 @@ export class PrometheusDatasource { return this.resultTransformer.getOriginalMetricName(labelData); } } + +export function alignRange(start, end, step) { + const alignedEnd = Math.ceil(end / step) * step; + const alignedStart = Math.floor(start / step) * step; + return { + end: alignedEnd, + start: alignedStart, + }; +} + +export function extractRuleMappingFromGroups(groups: any[]) { + return groups.reduce( + (mapping, group) => + group.rules.filter(rule => rule.type === 'recording').reduce( + (acc, rule) => ({ + ...acc, + [rule.name]: rule.query, + }), + mapping + ), + {} + ); +} + +export function prometheusRegularEscape(value) { + if (typeof value === 'string') { + return value.replace(/'/g, "\\\\'"); + } + return value; +} + +export function prometheusSpecialRegexEscape(value) { + if (typeof value === 'string') { + return prometheusRegularEscape(value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]+?.()]/g, '\\\\$&')); + } + return value; +} diff --git a/public/app/plugins/datasource/prometheus/types.ts b/public/app/plugins/datasource/prometheus/types.ts new file mode 100644 index 00000000000..5bdc687d774 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/types.ts @@ -0,0 +1,6 @@ +import { DataQuery } from '@grafana/ui/src/types'; + +export interface PromQuery extends DataQuery { + expr: string; +} + diff --git a/public/app/plugins/datasource/testdata/QueryEditor.tsx b/public/app/plugins/datasource/testdata/QueryEditor.tsx index 25a811da42d..ef49505d2c6 100644 --- a/public/app/plugins/datasource/testdata/QueryEditor.tsx +++ b/public/app/plugins/datasource/testdata/QueryEditor.tsx @@ -10,18 +10,17 @@ import { FormLabel, Select, SelectOptionItem } from '@grafana/ui'; // Types import { QueryEditorProps } from '@grafana/ui/src/types'; - -interface Scenario { - id: string; - name: string; -} +import { TestDataDatasource } from './datasource'; +import { TestDataQuery, Scenario } from './types'; interface State { scenarioList: Scenario[]; current: Scenario | null; } -export class QueryEditor extends PureComponent { +type Props = QueryEditorProps; + +export class QueryEditor extends PureComponent { backendSrv: BackendSrv = getBackendSrv(); state: State = { diff --git a/public/app/plugins/datasource/testdata/datasource.ts b/public/app/plugins/datasource/testdata/datasource.ts index 989209792fb..c329389ee8c 100644 --- a/public/app/plugins/datasource/testdata/datasource.ts +++ b/public/app/plugins/datasource/testdata/datasource.ts @@ -1,15 +1,17 @@ import _ from 'lodash'; import TableModel from 'app/core/table_model'; +import { DataSourceApi, DataQueryOptions } from '@grafana/ui'; +import { TestDataQuery } from './types'; -class TestDataDatasource { - id: any; +export class TestDataDatasource implements DataSourceApi { + id: number; /** @ngInject */ constructor(instanceSettings, private backendSrv, private $q) { this.id = instanceSettings.id; } - query(options) { + query(options: DataQueryOptions) { const queries = _.filter(options.targets, item => { return item.hide !== true; }).map(item => { @@ -93,4 +95,3 @@ class TestDataDatasource { } } -export { TestDataDatasource }; diff --git a/public/app/plugins/datasource/testdata/module.ts b/public/app/plugins/datasource/testdata/module.ts index d3b376e3307..efd6c207407 100644 --- a/public/app/plugins/datasource/testdata/module.ts +++ b/public/app/plugins/datasource/testdata/module.ts @@ -1,6 +1,6 @@ import { TestDataDatasource } from './datasource'; -import { TestDataQueryCtrl } from './query_ctrl'; -// import { QueryEditor } from './QueryEditor'; +// import { TestDataQueryCtrl } from './query_ctrl'; +import { QueryEditor } from './QueryEditor'; class TestDataAnnotationsQueryCtrl { annotation: any; @@ -11,8 +11,8 @@ class TestDataAnnotationsQueryCtrl { } export { - // QueryEditor, + QueryEditor, TestDataDatasource as Datasource, - TestDataQueryCtrl as QueryCtrl, + // TestDataQueryCtrl as QueryCtrl, TestDataAnnotationsQueryCtrl as AnnotationsQueryCtrl, }; diff --git a/public/app/plugins/datasource/testdata/types.ts b/public/app/plugins/datasource/testdata/types.ts new file mode 100644 index 00000000000..e233c3ef7a0 --- /dev/null +++ b/public/app/plugins/datasource/testdata/types.ts @@ -0,0 +1,11 @@ +import { DataQuery } from '@grafana/ui/src/types'; + +export interface TestDataQuery extends DataQuery { + scenarioId: string; +} + +export interface Scenario { + id: string; + name: string; +} + From a69f79caed27b19b7f0652928823e51b4262f407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 17:43:58 +0100 Subject: [PATCH 20/27] wip: more typings --- public/app/features/dashboard/panel_model.ts | 2 -- public/app/plugins/datasource/loki/datasource.ts | 15 ++++++++++----- public/app/plugins/datasource/loki/types.ts | 6 ++++++ 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 public/app/plugins/datasource/loki/types.ts diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index b54cabc9012..b7e8a68c722 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -243,8 +243,6 @@ export class PanelModel { addQuery(query?: Partial) { query = query || { refId: 'A' }; query.refId = this.getNextQueryLetter(); - query.isNew = true; - this.targets.push(query); } diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 893ce649613..6c5bda8bcf2 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -1,13 +1,18 @@ +// Libraries import _ from 'lodash'; +// Services & Utils import * as dateMath from 'app/core/utils/datemath'; -import { LogsStream, LogsModel, makeSeriesForLogs } from 'app/core/logs_model'; -import { PluginMeta, DataQuery } from '@grafana/ui/src/types'; import { addLabelToSelector } from 'app/plugins/datasource/prometheus/add_label_to_query'; - import LanguageProvider from './language_provider'; import { mergeStreamsToLogs } from './result_transformer'; import { formatQuery, parseQuery } from './query_utils'; +import { makeSeriesForLogs } from 'app/core/logs_model'; + +// Types +import { LogsStream, LogsModel } from 'app/core/logs_model'; +import { PluginMeta, DataQueryOptions, DataSourceApi } from '@grafana/ui/src/types'; +import { LokiQuery } from './types'; export const DEFAULT_MAX_LINES = 1000; @@ -27,7 +32,7 @@ function serializeParams(data: any) { .join('&'); } -export default class LokiDatasource { +export default class LokiDatasource implements DataSourceApi { languageProvider: LanguageProvider; maxLines: number; @@ -68,7 +73,7 @@ export default class LokiDatasource { }; } - query(options): Promise<{ data: LogsStream[] }> { + query(options: DataQueryOptions): Promise<{ data: LogsStream[] }> { const queryTargets = options.targets .filter(target => target.expr) .map(target => this.prepareQueryTarget(target, options)); diff --git a/public/app/plugins/datasource/loki/types.ts b/public/app/plugins/datasource/loki/types.ts new file mode 100644 index 00000000000..7325239bb3a --- /dev/null +++ b/public/app/plugins/datasource/loki/types.ts @@ -0,0 +1,6 @@ +import { DataQuery } from '@grafana/ui/src/types'; + +export interface LokiQuery extends DataQuery { + expr: string; +} + From 1d2902715f33f06fc75a06f2c324a79d6b8e281f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 18:14:27 +0100 Subject: [PATCH 21/27] wip: progress on adding query types --- .../plugins/datasource/loki/datasource.test.ts | 14 +++++++++++--- public/app/plugins/datasource/loki/datasource.ts | 10 +++++----- .../plugins/datasource/loki/language_provider.ts | 16 +++++++++++----- public/app/types/explore.ts | 4 ++-- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index b7f67ffc0e7..8b84f1073fb 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1,3 +1,4 @@ +import moment from 'moment'; import LokiDatasource from './datasource'; describe('LokiDatasource', () => { @@ -13,12 +14,19 @@ describe('LokiDatasource', () => { replace: a => a, }; - const range = { from: 'now-6h', to: 'now' }; + const range = { + from: moment(), + to: moment(), + raw: { + from: 'now-6h', + to: 'now' + } + }; test('should use default max lines when no limit given', () => { const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(); - ds.query({ range, targets: [{ expr: 'foo' }] }); + ds.query({ range, targets: [{ expr: 'foo', refId: 'B' }] }); expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1); expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=1000'); }); @@ -28,7 +36,7 @@ describe('LokiDatasource', () => { const customSettings = { ...instanceSettings, jsonData: customData }; const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(); - ds.query({ range, targets: [{ expr: 'foo' }] }); + ds.query({ range, targets: [{ expr: 'foo', refId: 'A' }] }); expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1); expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=20'); }); diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 6c5bda8bcf2..95d7a2a830e 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -11,7 +11,7 @@ import { makeSeriesForLogs } from 'app/core/logs_model'; // Types import { LogsStream, LogsModel } from 'app/core/logs_model'; -import { PluginMeta, DataQueryOptions, DataSourceApi } from '@grafana/ui/src/types'; +import { PluginMeta, DataQueryOptions } from '@grafana/ui/src/types'; import { LokiQuery } from './types'; export const DEFAULT_MAX_LINES = 1000; @@ -32,7 +32,7 @@ function serializeParams(data: any) { .join('&'); } -export default class LokiDatasource implements DataSourceApi { +export default class LokiDatasource { languageProvider: LanguageProvider; maxLines: number; @@ -101,7 +101,7 @@ export default class LokiDatasource implements DataSourceApi { }); } - async importQueries(queries: DataQuery[], originMeta: PluginMeta): Promise { + async importQueries(queries: LokiQuery[], originMeta: PluginMeta): Promise { return this.languageProvider.importQueries(queries, originMeta.id); } @@ -114,7 +114,7 @@ export default class LokiDatasource implements DataSourceApi { }); } - modifyQuery(query: DataQuery, action: any): DataQuery { + modifyQuery(query: LokiQuery, action: any): LokiQuery { const parsed = parseQuery(query.expr || ''); let selector = parsed.query; switch (action.type) { @@ -129,7 +129,7 @@ export default class LokiDatasource implements DataSourceApi { return { ...query, expr: expression }; } - getHighlighterExpression(query: DataQuery): string { + getHighlighterExpression(query: LokiQuery): string { return parseQuery(query.expr).regexp; } diff --git a/public/app/plugins/datasource/loki/language_provider.ts b/public/app/plugins/datasource/loki/language_provider.ts index 3532e81ccd9..115a0a5f11f 100644 --- a/public/app/plugins/datasource/loki/language_provider.ts +++ b/public/app/plugins/datasource/loki/language_provider.ts @@ -1,6 +1,12 @@ +// Libraries import _ from 'lodash'; import moment from 'moment'; +// Services & Utils +import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils'; +import syntax from './syntax'; + +// Types import { CompletionItem, CompletionItemGroup, @@ -9,9 +15,7 @@ import { TypeaheadOutput, HistoryItem, } from 'app/types/explore'; -import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils'; -import syntax from './syntax'; -import { DataQuery } from '@grafana/ui/src/types'; +import { LokiQuery } from './types'; const DEFAULT_KEYS = ['job', 'namespace']; const EMPTY_SELECTOR = '{}'; @@ -20,7 +24,9 @@ const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h const wrapLabel = (label: string) => ({ label }); -export function addHistoryMetadata(item: CompletionItem, history: HistoryItem[]): CompletionItem { +type LokiHistoryItem = HistoryItem; + +export function addHistoryMetadata(item: CompletionItem, history: LokiHistoryItem[]): CompletionItem { const cutoffTs = Date.now() - HISTORY_COUNT_CUTOFF; const historyForItem = history.filter(h => h.ts > cutoffTs && (h.query.expr as string) === item.label); const count = historyForItem.length; @@ -155,7 +161,7 @@ export default class LokiLanguageProvider extends LanguageProvider { return { context, refresher, suggestions }; } - async importQueries(queries: DataQuery[], datasourceType: string): Promise { + async importQueries(queries: LokiQuery[], datasourceType: string): Promise { if (datasourceType === 'prometheus') { return Promise.all( queries.map(async query => { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index e5ae676ba25..c69e93ff88e 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -243,9 +243,9 @@ export interface ExploreUrlState { range: RawTimeRange; } -export interface HistoryItem { +export interface HistoryItem { ts: number; - query: DataQuery; + query: TQuery; } export abstract class LanguageProvider { From 5d17ad110399fb71aa50880948e8f9aa3c590dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 18:59:32 +0100 Subject: [PATCH 22/27] more typings work around data query and data source --- packages/grafana-ui/src/types/datasource.ts | 18 +++++++++++++ public/app/core/utils/explore.ts | 8 ++++-- .../loki/components/LokiQueryField.tsx | 15 +++++++---- .../datasource/loki/datasource.test.ts | 22 ++++++++-------- public/test/helpers/getQueryOptions.ts | 25 +++++++++++++++++++ 5 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 public/test/helpers/getQueryOptions.ts diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index cb6115486b1..ffcbbb5fe64 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -7,8 +7,26 @@ export interface DataQueryResponse { } export interface DataQuery { + /** + * A - Z + */ refId: string; + + /** + * true if query is disabled (ie not executed / sent to TSDB) + */ hide?: boolean; + + /** + * Unique, guid like, string used in explore mode + */ + key?: string; + + /** + * For mixed data sources the selected datasource is on the query level. + * For non mixed scenarios this is undefined. + */ + datasource?: string | null; } export interface DataQueryOptions { diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 2d1c0d2ad71..45b70672bc6 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -203,7 +203,7 @@ export function ensureQueries(queries?: DataQuery[]): DataQuery[] { /** * A target is non-empty when it has keys (with non-empty values) other than refId and key. */ -export function hasNonEmptyQuery(queries: DataQuery[]): boolean { +export function hasNonEmptyQuery(queries: TQuery[]): boolean { return ( queries && queries.some( @@ -280,7 +280,11 @@ export function makeTimeSeriesList(dataList) { /** * Update the query history. Side-effect: store history in local storage */ -export function updateHistory(history: HistoryItem[], datasourceId: string, queries: DataQuery[]): HistoryItem[] { +export function updateHistory( + history: Array>, + datasourceId: string, + queries: T[] +): Array> { const ts = Date.now(); queries.forEach(query => { history = [{ query, ts }, ...history]; diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index 98c8a5f6da9..febb322acca 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -1,16 +1,21 @@ +// Libraries import React from 'react'; import Cascader from 'rc-cascader'; import PluginPrism from 'slate-prism'; import Prism from 'prismjs'; -import { DataQuery } from '@grafana/ui/src/types'; -import { TypeaheadOutput } from 'app/types/explore'; +// Components +import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; +// Utils & Services // dom also includes Element polyfills import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom'; import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; -import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; + +// Types +import { LokiQuery } from '../types'; +import { TypeaheadOutput } from 'app/types/explore'; const PRISM_SYNTAX = 'promql'; @@ -63,10 +68,10 @@ interface LokiQueryFieldProps { error?: string | JSX.Element; hint?: any; history?: any[]; - initialQuery?: DataQuery; + initialQuery?: LokiQuery; onClickHintFix?: (action: any) => void; onPressEnter?: () => void; - onQueryChange?: (value: DataQuery, override?: boolean) => void; + onQueryChange?: (value: LokiQuery, override?: boolean) => void; } interface LokiQueryFieldState { diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 8b84f1073fb..195ac194dad 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1,5 +1,6 @@ -import moment from 'moment'; import LokiDatasource from './datasource'; +import { LokiQuery } from './types'; +import { getQueryOptions } from 'test/helpers/getQueryOptions'; describe('LokiDatasource', () => { const instanceSettings: any = { @@ -14,19 +15,13 @@ describe('LokiDatasource', () => { replace: a => a, }; - const range = { - from: moment(), - to: moment(), - raw: { - from: 'now-6h', - to: 'now' - } - }; - test('should use default max lines when no limit given', () => { const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(); - ds.query({ range, targets: [{ expr: 'foo', refId: 'B' }] }); + const options = getQueryOptions({ targets: [{ expr: 'foo', refId: 'B' }] }); + + ds.query(options); + expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1); expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=1000'); }); @@ -36,7 +31,10 @@ describe('LokiDatasource', () => { const customSettings = { ...instanceSettings, jsonData: customData }; const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(); - ds.query({ range, targets: [{ expr: 'foo', refId: 'A' }] }); + + const options = getQueryOptions({ targets: [{ expr: 'foo', refId: 'B' }] }); + ds.query(options); + expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1); expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=20'); }); diff --git a/public/test/helpers/getQueryOptions.ts b/public/test/helpers/getQueryOptions.ts new file mode 100644 index 00000000000..ac92c2afb55 --- /dev/null +++ b/public/test/helpers/getQueryOptions.ts @@ -0,0 +1,25 @@ +import { DataQueryOptions, DataQuery } from '@grafana/ui'; +import moment from 'moment'; + + +export function getQueryOptions(options: Partial>): DataQueryOptions { + const raw = {from: 'now', to: 'now-1h'}; + const range = { from: moment(), to: moment(), raw: raw}; + + const defaults: DataQueryOptions = { + range: range, + rangeRaw: raw, + targets: [], + scopedVars: {}, + timezone: 'browser', + panelId: 1, + dashboardId: 1, + interval: '60s', + intervalMs: 60000, + maxDataPoints: 500, + }; + + Object.assign(defaults, options); + + return defaults; +} From 5ab9a7c2b8809a9e73aeb1ed754000daacace79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 19:11:30 +0100 Subject: [PATCH 23/27] Further refinements of typings --- packages/grafana-ui/src/types/plugin.ts | 20 ++++++++++--------- .../datasource/testdata/QueryEditor.tsx | 5 +++-- .../plugins/datasource/testdata/datasource.ts | 6 +++++- .../app/plugins/datasource/testdata/module.ts | 8 ++++---- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index 8ded328b2da..420a54e5840 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -3,11 +3,6 @@ import { PanelProps, PanelOptionsProps } from './panel'; import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource'; export interface DataSourceApi { - // set externally by grafana - name?: string; - meta?: PluginMeta; - pluginExports?: PluginExports; - /** * min interval range */ @@ -37,19 +32,26 @@ export interface DataSourceApi { * Get hints for query improvements */ getQueryHints?(query: TQuery, results: any[], ...rest: any): QueryHint[]; + + /** + * Set after constructor is called by Grafana + */ + name?: string; + meta?: PluginMeta; + pluginExports?: PluginExports; } -export interface QueryEditorProps { +export interface QueryEditorProps { datasource: DSType; query: TQuery; onExecuteQuery?: () => void; - onQueryChange?: (value: DataQuery) => void; + onQueryChange?: (value: TQuery) => void; } export interface PluginExports { - Datasource?: any; + Datasource?: DataSourceApi; QueryCtrl?: any; - QueryEditor?: ComponentClass; + QueryEditor?: ComponentClass>; ConfigCtrl?: any; AnnotationsQueryCtrl?: any; VariableQueryEditor?: any; diff --git a/public/app/plugins/datasource/testdata/QueryEditor.tsx b/public/app/plugins/datasource/testdata/QueryEditor.tsx index ef49505d2c6..20b86d571df 100644 --- a/public/app/plugins/datasource/testdata/QueryEditor.tsx +++ b/public/app/plugins/datasource/testdata/QueryEditor.tsx @@ -29,11 +29,12 @@ export class QueryEditor extends PureComponent { }; async componentDidMount() { - const { query } = this.props; + const { query, datasource } = this.props; query.scenarioId = query.scenarioId || 'random_walk'; - const scenarioList = await this.backendSrv.get('/api/tsdb/testdata/scenarios'); + // const scenarioList = await this.backendSrv.get('/api/tsdb/testdata/scenarios'); + const scenarioList = await datasource.getScenarios(); const current = _.find(scenarioList, { id: query.scenarioId }); this.setState({ scenarioList: scenarioList, current: current }); diff --git a/public/app/plugins/datasource/testdata/datasource.ts b/public/app/plugins/datasource/testdata/datasource.ts index c329389ee8c..6ba0da59457 100644 --- a/public/app/plugins/datasource/testdata/datasource.ts +++ b/public/app/plugins/datasource/testdata/datasource.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import TableModel from 'app/core/table_model'; import { DataSourceApi, DataQueryOptions } from '@grafana/ui'; -import { TestDataQuery } from './types'; +import { TestDataQuery, Scenario } from './types'; export class TestDataDatasource implements DataSourceApi { id: number; @@ -93,5 +93,9 @@ export class TestDataDatasource implements DataSourceApi { message: 'Data source is working', }); } + + getScenarios(): Promise { + return this.backendSrv.get('/api/tsdb/testdata/scenarios'); + } } diff --git a/public/app/plugins/datasource/testdata/module.ts b/public/app/plugins/datasource/testdata/module.ts index efd6c207407..d3b376e3307 100644 --- a/public/app/plugins/datasource/testdata/module.ts +++ b/public/app/plugins/datasource/testdata/module.ts @@ -1,6 +1,6 @@ import { TestDataDatasource } from './datasource'; -// import { TestDataQueryCtrl } from './query_ctrl'; -import { QueryEditor } from './QueryEditor'; +import { TestDataQueryCtrl } from './query_ctrl'; +// import { QueryEditor } from './QueryEditor'; class TestDataAnnotationsQueryCtrl { annotation: any; @@ -11,8 +11,8 @@ class TestDataAnnotationsQueryCtrl { } export { - QueryEditor, + // QueryEditor, TestDataDatasource as Datasource, - // TestDataQueryCtrl as QueryCtrl, + TestDataQueryCtrl as QueryCtrl, TestDataAnnotationsQueryCtrl as AnnotationsQueryCtrl, }; From 12468c6033811bae9a21ab1ba09a994a538fce40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 19:14:22 +0100 Subject: [PATCH 24/27] Delete .all.ts@neomake_22624_74.ts --- public/app/features/.all.ts@neomake_22624_74.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 public/app/features/.all.ts@neomake_22624_74.ts diff --git a/public/app/features/.all.ts@neomake_22624_74.ts b/public/app/features/.all.ts@neomake_22624_74.ts deleted file mode 100644 index 99de12bf215..00000000000 --- a/public/app/features/.all.ts@neomake_22624_74.ts +++ /dev/null @@ -1,14 +0,0 @@ -import './annotations/all'; -import './templating/all'; -import './plugins/all'; -import './dashboard/all'; -import './playlist/all'; -import './panel/all'; -import './org/all'; -import './admin'; -import './alerting/NotificationsEditCtrl'; -import './alerting/NotificationsListCtrl'; -import './manage-dashboards'; -import './teams/CreateTeamCtrl'; -import './profile/all'; -import './datasources/settings/dsHttpSettings'; From 16d476d22898b8a599d570107f2d955506d1fb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 18 Jan 2019 19:22:40 +0100 Subject: [PATCH 25/27] Query editor row style update & sass cleanup --- .../dashboard/panel_editor/QueryInspector.tsx | 16 +---- public/sass/components/_query_editor.scss | 68 +------------------ 2 files changed, 3 insertions(+), 81 deletions(-) diff --git a/public/app/features/dashboard/panel_editor/QueryInspector.tsx b/public/app/features/dashboard/panel_editor/QueryInspector.tsx index 8e490f6b622..25c3c68e21e 100644 --- a/public/app/features/dashboard/panel_editor/QueryInspector.tsx +++ b/public/app/features/dashboard/panel_editor/QueryInspector.tsx @@ -177,7 +177,6 @@ export class QueryInspector extends PureComponent { render() { const { response, isLoading } = this.state.dsQuery; - const { isMocking } = this.state; const openNodes = this.getNrOfOpenNodes(); if (isLoading) { @@ -199,20 +198,7 @@ export class QueryInspector extends PureComponent {
- {!isMocking && } - {isMocking && ( -
-
-