Commit a28aefa3 by David Committed by GitHub

Graph: constant series as override (#19102)

* Graph: constant series as override

- ability to define a series override that takes the first value of a
timeseries and produces constant series from it.

* Moved constant series override logic to grafana-ui

* Added docs for instant queries and constant series override
parent 44a2a648
...@@ -51,10 +51,22 @@ Open a graph in edit mode by click the title > Edit (or by pressing `e` key whil ...@@ -51,10 +51,22 @@ Open a graph in edit mode by click the title > Edit (or by pressing `e` key whil
| _Resolution_ | Controls the step option. Small steps create high-resolution graphs but can be slow over larger time ranges, lowering the resolution can speed things up. `1/2` will try to set step option to generate 1 data point for every other pixel. A value of `1/10` will try to set step option so there is a data point every 10 pixels. | | _Resolution_ | Controls the step option. Small steps create high-resolution graphs but can be slow over larger time ranges, lowering the resolution can speed things up. `1/2` will try to set step option to generate 1 data point for every other pixel. A value of `1/10` will try to set step option so there is a data point every 10 pixels. |
| _Metric lookup_ | Search for metric names in this input field. | | _Metric lookup_ | Search for metric names in this input field. |
| _Format as_ | Switch between Table, Time series or Heatmap. Table format will only work in the Table panel. Heatmap format is suitable for displaying metrics having histogram type on Heatmap panel. Under the hood, it converts cumulative histogram to regular and sorts series by the bucket bound. | | _Format as_ | Switch between Table, Time series or Heatmap. Table format will only work in the Table panel. Heatmap format is suitable for displaying metrics having histogram type on Heatmap panel. Under the hood, it converts cumulative histogram to regular and sorts series by the bucket bound. |
| _Instant_ | Perform an "instant" query, to return only the latest value that Prometheus has scraped for the requested time series. Instant queries return results much faster than normal range queries. Use them to look up label sets. |
> NOTE: Grafana slightly modifies the request dates for queries to align them with the dynamically calculated step. > NOTE: Grafana slightly modifies the request dates for queries to align them with the dynamically calculated step.
> This ensures consistent display of metrics data but can result in a small gap of data at the right edge of a graph. > This ensures consistent display of metrics data but can result in a small gap of data at the right edge of a graph.
### Instant queries
The Prometheus datasource allows you to run "instant" queries, which queries only the latest value.
You can visualize the results in a table panel to see all available labels of a timeseries.
Instant query results are made up only of one datapoint per series but can be shown in the graph panel with the help of [series overrides](/features/panels/graph/#series-overrides).
To show them in the graph as a latest value point, add a series override and select `Points > true`.
To show a horizontal line across the whole graph, add a series override and select `Transform > constant`.
> Support for constant series overrides is available from Grafana v6.4
## Templating ## Templating
Instead of hard-coding things like server, application and sensor name in your metric queries, you can use variables in their place. Instead of hard-coding things like server, application and sensor name in your metric queries, you can use variables in their place.
......
import { getFlotPairs } from './flotPairs'; import { getFlotPairs, getFlotPairsConstant } from './flotPairs';
import { MutableDataFrame } from '@grafana/data'; import { MutableDataFrame, TimeRange, dateTime } from '@grafana/data';
describe('getFlotPairs', () => { describe('getFlotPairs', () => {
const series = new MutableDataFrame({ const series = new MutableDataFrame({
...@@ -33,3 +33,28 @@ describe('getFlotPairs', () => { ...@@ -33,3 +33,28 @@ describe('getFlotPairs', () => {
expect(pairs[0][1]).toEqual('a'); expect(pairs[0][1]).toEqual('a');
}); });
}); });
describe('getFlotPairsConstant', () => {
const makeRange = (from: number, to: number): TimeRange => ({
from: dateTime(from),
to: dateTime(to),
raw: { from: `${from}`, to: `${to}` },
});
it('should return an empty series on empty data', () => {
const range: TimeRange = makeRange(0, 1);
const pairs = getFlotPairsConstant([], range);
expect(pairs).toMatchObject([]);
});
it('should return an empty series on missing range', () => {
const pairs = getFlotPairsConstant([], {} as TimeRange);
expect(pairs).toMatchObject([]);
});
it('should return an constant series for range', () => {
const range: TimeRange = makeRange(0, 1);
const pairs = getFlotPairsConstant([[2, 123], [4, 456]], range);
expect(pairs).toMatchObject([[0, 123], [1, 123]]);
});
});
// Types // Types
import { NullValueMode, GraphSeriesValue, Field } from '@grafana/data'; import { NullValueMode, GraphSeriesValue, Field, TimeRange } from '@grafana/data';
export interface FlotPairsOptions { export interface FlotPairsOptions {
xField: Field; xField: Field;
...@@ -42,3 +42,19 @@ export function getFlotPairs({ xField, yField, nullValueMode }: FlotPairsOptions ...@@ -42,3 +42,19 @@ export function getFlotPairs({ xField, yField, nullValueMode }: FlotPairsOptions
} }
return pairs; return pairs;
} }
/**
* Returns a constant series based on the first value from the provide series.
* @param seriesData Series
* @param range Start and end time for the constant series
*/
export function getFlotPairsConstant(seriesData: GraphSeriesValue[][], range: TimeRange): GraphSeriesValue[][] {
if (!range.from || !range.to || !seriesData || seriesData.length === 0) {
return [];
}
const from = range.from.valueOf();
const to = range.to.valueOf();
const value = seriesData[0][1];
return [[from, value], [to, value]];
}
...@@ -4,7 +4,7 @@ export * from './namedColorsPalette'; ...@@ -4,7 +4,7 @@ export * from './namedColorsPalette';
export * from './displayProcessor'; export * from './displayProcessor';
export * from './fieldDisplay'; export * from './fieldDisplay';
export * from './validate'; export * from './validate';
export { getFlotPairs } from './flotPairs'; export { getFlotPairs, getFlotPairsConstant } from './flotPairs';
export * from './slate'; export * from './slate';
export * from './dataLinks'; export * from './dataLinks';
export { default as ansicolor } from './ansicolor'; export { default as ansicolor } from './ansicolor';
......
...@@ -24,7 +24,14 @@ import ReactDOM from 'react-dom'; ...@@ -24,7 +24,14 @@ import ReactDOM from 'react-dom';
import { GraphLegendProps, Legend } from './Legend/Legend'; import { GraphLegendProps, Legend } from './Legend/Legend';
import { GraphCtrl } from './module'; import { GraphCtrl } from './module';
import { getValueFormat, ContextMenuGroup, FieldDisplay, ContextMenuItem, getDisplayProcessor } from '@grafana/ui'; import {
getValueFormat,
ContextMenuGroup,
FieldDisplay,
ContextMenuItem,
getDisplayProcessor,
getFlotPairsConstant,
} from '@grafana/ui';
import { provideTheme, getCurrentTheme } from 'app/core/utils/ConfigProvider'; import { provideTheme, getCurrentTheme } from 'app/core/utils/ConfigProvider';
import { toUtc, LinkModelSupplier, DataFrameView } from '@grafana/data'; import { toUtc, LinkModelSupplier, DataFrameView } from '@grafana/data';
import { GraphContextMenuCtrl } from './GraphContextMenuCtrl'; import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
...@@ -395,6 +402,10 @@ class GraphElement { ...@@ -395,6 +402,10 @@ class GraphElement {
const series = data[i]; const series = data[i];
series.data = series.getFlotPairs(series.nullPointMode || this.panel.nullPointMode); series.data = series.getFlotPairs(series.nullPointMode || this.panel.nullPointMode);
if (series.transform === 'constant') {
series.data = getFlotPairsConstant(series.data, this.ctrl.range);
}
// if hidden remove points and disable stack // if hidden remove points and disable stack
if (this.ctrl.hiddenSeries[series.alias]) { if (this.ctrl.hiddenSeries[series.alias]) {
series.data = []; series.data = [];
......
...@@ -152,7 +152,7 @@ export function SeriesOverridesCtrl($scope: any, $element: JQuery, popoverSrv: a ...@@ -152,7 +152,7 @@ export function SeriesOverridesCtrl($scope: any, $element: JQuery, popoverSrv: a
$scope.addOverrideOption('Color', 'color', ['change']); $scope.addOverrideOption('Color', 'color', ['change']);
$scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]); $scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]);
$scope.addOverrideOption('Z-index', 'zindex', [-3, -2, -1, 0, 1, 2, 3]); $scope.addOverrideOption('Z-index', 'zindex', [-3, -2, -1, 0, 1, 2, 3]);
$scope.addOverrideOption('Transform', 'transform', ['negative-Y']); $scope.addOverrideOption('Transform', 'transform', ['constant', 'negative-Y']);
$scope.addOverrideOption('Legend', 'legend', [true, false]); $scope.addOverrideOption('Legend', 'legend', [true, false]);
$scope.addOverrideOption('Hide in tooltip', 'hideTooltip', [true, false]); $scope.addOverrideOption('Hide in tooltip', 'hideTooltip', [true, false]);
$scope.updateCurrentOverrides(); $scope.updateCurrentOverrides();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment