feat(testdata): worked on testdata app

This commit is contained in:
Torkel Ödegaard
2016-09-27 14:39:51 +02:00
parent 81cb4a740b
commit 34f15d92d0
30 changed files with 512 additions and 169 deletions

View File

@ -97,12 +97,7 @@ func (slice DataSourceList) Swap(i, j int) {
}
type MetricQueryResultDto struct {
Data []MetricQueryResultDataDto `json:"data"`
}
type MetricQueryResultDataDto struct {
Target string `json:"target"`
DataPoints [][2]float64 `json:"datapoints"`
Data []interface{} `json:"data"`
}
type UserStars struct {

View File

@ -165,7 +165,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
}
}
if c.OrgRole == m.ROLE_ADMIN {
if len(appLink.Children) > 0 && c.OrgRole == m.ROLE_ADMIN {
appLink.Children = append(appLink.Children, &dtos.NavLink{Divider: true})
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "fa fa-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
}

View File

@ -2,39 +2,49 @@ package api
import (
"encoding/json"
"math/rand"
"net/http"
"strconv"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util"
)
func GetTestMetrics(c *middleware.Context) Response {
from := c.QueryInt64("from")
to := c.QueryInt64("to")
maxDataPoints := c.QueryInt64("maxDataPoints")
stepInSeconds := (to - from) / maxDataPoints
result := dtos.MetricQueryResultDto{}
result.Data = make([]dtos.MetricQueryResultDataDto, 1)
timeRange := tsdb.NewTimeRange(c.Query("from"), c.Query("to"))
for seriesIndex := range result.Data {
points := make([][2]float64, maxDataPoints)
walker := rand.Float64() * 100
time := from
for i := range points {
points[i][0] = walker
points[i][1] = float64(time)
walker += rand.Float64() - 0.5
time += stepInSeconds
req := &tsdb.Request{
TimeRange: timeRange,
Queries: []*tsdb.Query{
{
RefId: "A",
MaxDataPoints: c.QueryInt64("maxDataPoints"),
IntervalMs: c.QueryInt64("intervalMs"),
DataSource: &tsdb.DataSourceInfo{
Name: "Grafana TestDataDB",
PluginId: "grafana-testdata-datasource",
},
},
},
}
result.Data[seriesIndex].Target = "test-series-" + strconv.Itoa(seriesIndex)
result.Data[seriesIndex].DataPoints = points
resp, err := tsdb.HandleRequest(req)
if err != nil {
return ApiError(500, "Metric request error", err)
}
result := dtos.MetricQueryResultDto{}
for _, v := range resp.Results {
if v.Error != nil {
return ApiError(500, "tsdb.HandleRequest() response error", v.Error)
}
for _, series := range v.Series {
result.Data = append(result.Data, series)
}
}
return Json(200, &result)

View File

@ -43,7 +43,12 @@ func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
appSubPath := strings.Replace(fp.PluginDir, app.PluginDir, "", 1)
fp.IncludedInAppId = app.Id
fp.BaseUrl = app.BaseUrl
if isExternalPlugin(app.PluginDir) {
fp.Module = util.JoinUrlFragments("plugins/"+app.Id, appSubPath) + "/module"
} else {
fp.Module = util.JoinUrlFragments("app/plugins/app/"+app.Id, appSubPath) + "/module"
}
}
func (fp *FrontendPluginBase) handleModuleDefaults() {

View File

@ -34,8 +34,8 @@ type AlertQuery struct {
}
func (c *QueryCondition) Eval(context *alerting.EvalContext) {
timerange := tsdb.NewTimerange(c.Query.From, c.Query.To)
seriesList, err := c.executeQuery(context, timerange)
timeRange := tsdb.NewTimeRange(c.Query.From, c.Query.To)
seriesList, err := c.executeQuery(context, timeRange)
if err != nil {
context.Error = err
return
@ -69,7 +69,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) {
context.Firing = len(context.EvalMatches) > 0
}
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timerange tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) {
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) {
getDsInfo := &m.GetDataSourceByIdQuery{
Id: c.Query.DatasourceId,
OrgId: context.Rule.OrgId,
@ -79,7 +79,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timerange t
return nil, fmt.Errorf("Could not find datasource")
}
req := c.getRequestForAlertRule(getDsInfo.Result, timerange)
req := c.getRequestForAlertRule(getDsInfo.Result, timeRange)
result := make(tsdb.TimeSeriesSlice, 0)
resp, err := c.HandleRequest(req)

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
_ "github.com/grafana/grafana/pkg/tsdb/graphite"
_ "github.com/grafana/grafana/pkg/tsdb/prometheus"
_ "github.com/grafana/grafana/pkg/tsdb/testdata"
)
var engine *alerting.Engine

View File

@ -10,13 +10,14 @@ type Query struct {
DataSource *DataSourceInfo
Results []*TimeSeries
Exclude bool
MaxDataPoints int64
IntervalMs int64
}
type QuerySlice []*Query
type Request struct {
TimeRange TimeRange
MaxDataPoints int
Queries QuerySlice
}

54
pkg/tsdb/testdata/testdata.go vendored Normal file
View File

@ -0,0 +1,54 @@
package testdata
import (
"math/rand"
"github.com/grafana/grafana/pkg/tsdb"
)
type TestDataExecutor struct {
*tsdb.DataSourceInfo
}
func NewTestDataExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor {
return &TestDataExecutor{dsInfo}
}
func init() {
tsdb.RegisterExecutor("grafana-testdata-datasource", NewTestDataExecutor)
}
func (e *TestDataExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
result := &tsdb.BatchResult{}
result.QueryResults = make(map[string]*tsdb.QueryResult)
from, _ := context.TimeRange.FromTime()
to, _ := context.TimeRange.ToTime()
queryRes := &tsdb.QueryResult{}
for _, query := range queries {
// scenario := query.Model.Get("scenario").MustString("random_walk")
series := &tsdb.TimeSeries{Name: "test-series-0"}
stepInSeconds := (to.Unix() - from.Unix()) / query.MaxDataPoints
points := make([][2]*float64, 0)
walker := rand.Float64() * 100
time := from.Unix()
for i := int64(0); i < query.MaxDataPoints; i++ {
timestamp := float64(time)
val := float64(walker)
points = append(points, [2]*float64{&val, &timestamp})
walker += rand.Float64() - 0.5
time += stepInSeconds
}
series.Points = points
queryRes.Series = append(queryRes.Series, series)
}
result.QueryResults["A"] = queryRes
return result
}

View File

@ -2,11 +2,12 @@ package tsdb
import (
"fmt"
"strconv"
"strings"
"time"
)
func NewTimerange(from, to string) TimeRange {
func NewTimeRange(from, to string) TimeRange {
return TimeRange{
From: from,
To: to,
@ -21,6 +22,10 @@ type TimeRange struct {
}
func (tr TimeRange) FromTime() (time.Time, error) {
if val, err := strconv.ParseInt(tr.From, 10, 64); err == nil {
return time.Unix(val, 0), nil
}
fromRaw := strings.Replace(tr.From, "now-", "", 1)
diff, err := time.ParseDuration("-" + fromRaw)
@ -45,5 +50,9 @@ func (tr TimeRange) ToTime() (time.Time, error) {
return tr.Now.Add(diff), nil
}
if val, err := strconv.ParseInt(tr.To, 10, 64); err == nil {
return time.Unix(val, 0), nil
}
return time.Time{}, fmt.Errorf("cannot parse to value %s", tr.To)
}

View File

@ -60,6 +60,23 @@ func TestTimeRange(t *testing.T) {
})
})
Convey("can parse unix epocs", func() {
var err error
tr := TimeRange{
From: "1474973725473",
To: "1474975757930",
Now: now,
}
res, err := tr.FromTime()
So(err, ShouldBeNil)
So(res.Unix(), ShouldEqual, 1474973725473)
res, err = tr.ToTime()
So(err, ShouldBeNil)
So(res.Unix(), ShouldEqual, 1474975757930)
})
Convey("Cannot parse asdf", func() {
var err error
tr := TimeRange{

View File

@ -31,6 +31,8 @@ export default class TimeSeries {
allIsZero: boolean;
decimals: number;
scaledDecimals: number;
hasMsResolution: boolean;
isOutsideRange: boolean;
lines: any;
bars: any;
@ -54,6 +56,7 @@ export default class TimeSeries {
this.stats = {};
this.legend = true;
this.unit = opts.unit;
this.hasMsResolution = this.isMsResolutionNeeded();
}
applySeriesOverrides(overrides) {

View File

@ -174,7 +174,10 @@ function($, _, moment) {
lowLimitMs = kbn.interval_to_ms(lowLimitInterval);
}
else {
return userInterval;
return {
intervalMs: kbn.interval_to_ms(userInterval),
interval: userInterval,
};
}
}
@ -183,7 +186,10 @@ function($, _, moment) {
intervalMs = lowLimitMs;
}
return kbn.secondsToHms(intervalMs / 1000);
return {
intervalMs: intervalMs,
interval: kbn.secondsToHms(intervalMs / 1000),
};
};
kbn.describe_interval = function (string) {

View File

@ -25,6 +25,7 @@ class MetricsPanelCtrl extends PanelCtrl {
range: any;
rangeRaw: any;
interval: any;
intervalMs: any;
resolution: any;
timeInfo: any;
skipDataOnInit: boolean;
@ -123,11 +124,22 @@ class MetricsPanelCtrl extends PanelCtrl {
this.resolution = Math.ceil($(window).width() * (this.panel.span / 12));
}
var panelInterval = this.panel.interval;
var datasourceInterval = (this.datasource || {}).interval;
this.interval = kbn.calculateInterval(this.range, this.resolution, panelInterval || datasourceInterval);
this.calculateInterval();
};
calculateInterval() {
var intervalOverride = this.panel.interval;
// if no panel interval check datasource
if (!intervalOverride && this.datasource && this.datasource.interval) {
intervalOverride = this.datasource.interval;
}
var res = kbn.calculateInterval(this.range, this.resolution, intervalOverride);
this.interval = res.interval;
this.intervalMs = res.intervalMs;
}
applyPanelTimeOverrides() {
this.timeInfo = '';
@ -183,6 +195,7 @@ class MetricsPanelCtrl extends PanelCtrl {
range: this.range,
rangeRaw: this.rangeRaw,
interval: this.interval,
intervalMs: this.intervalMs,
targets: this.panel.targets,
format: this.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: this.resolution,

View File

@ -54,8 +54,8 @@ export class IntervalVariable implements Variable {
this.options.unshift({ text: 'auto', value: '$__auto_interval' });
}
var interval = kbn.calculateInterval(this.timeSrv.timeRange(), this.auto_count, (this.auto_min ? ">"+this.auto_min : null));
this.templateSrv.setGrafanaVariable('$__auto_interval', interval);
var res = kbn.calculateInterval(this.timeSrv.timeRange(), this.auto_count, (this.auto_min ? ">"+this.auto_min : null));
this.templateSrv.setGrafanaVariable('$__auto_interval', res.interval);
}
updateOptions() {

View File

@ -0,0 +1,5 @@
{
"title": "TestData - Graph Panel Last 1h",
"tags": ["testdata"],
"revision": 1
}

View File

@ -0,0 +1,45 @@
///<reference path="../../../../headers/common.d.ts" />
import _ from 'lodash';
class TestDataDatasource {
/** @ngInject */
constructor(private backendSrv, private $q) {}
query(options) {
var queries = _.filter(options.targets, item => {
return item.hide !== true;
});
if (queries.length === 0) {
return this.$q.when({data: []});
}
return this.backendSrv.get('/api/metrics/test', {
from: options.range.from.valueOf(),
to: options.range.to.valueOf(),
scenario: options.targets[0].scenario,
interval: options.intervalMs,
maxDataPoints: options.maxDataPoints,
}).then(res => {
res.data = res.data.map(item => {
return {target: item.name, datapoints: item.points};
});
return res;
});
}
annotationQuery(options) {
return this.backendSrv.get('/api/annotations', {
from: options.range.from.valueOf(),
to: options.range.to.valueOf(),
limit: options.limit,
type: options.type,
});
}
}
export {TestDataDatasource};

View File

@ -0,0 +1,22 @@
///<reference path="../../../../headers/common.d.ts" />
import {TestDataDatasource} from './datasource';
import {TestDataQueryCtrl} from './query_ctrl';
class TestDataAnnotationsQueryCtrl {
annotation: any;
constructor() {
}
static template = '<h2>test data</h2>';
}
export {
TestDataDatasource,
TestDataDatasource as Datasource,
TestDataQueryCtrl as QueryCtrl,
TestDataAnnotationsQueryCtrl as AnnotationsQueryCtrl,
};

View File

@ -0,0 +1,19 @@
{
"type": "datasource",
"name": "Grafana TestDataDB",
"id": "grafana-testdata-datasource",
"metrics": true,
"annotations": true,
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
"logos": {
"small": "",
"large": ""
}
}
}

View File

@ -0,0 +1,24 @@
///<reference path="../../../../headers/common.d.ts" />
import {TestDataDatasource} from './datasource';
import {QueryCtrl} from 'app/plugins/sdk';
export class TestDataQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html';
scenarioDefs: any;
/** @ngInject **/
constructor($scope, $injector) {
super($scope, $injector);
this.target.scenario = this.target.scenario || 'random_walk';
this.scenarioDefs = {
'random_walk': {text: 'Random Walk'},
'no_datapoints': {text: 'No Datapoints'},
'data_outside_range': {text: 'Data Outside Range'},
};
}
}

View File

@ -0,0 +1,36 @@
///<reference path="../../../headers/common.d.ts" />
export class ConfigCtrl {
static template = '';
appEditCtrl: any;
constructor(private backendSrv) {
this.appEditCtrl.setPreUpdateHook(this.initDatasource.bind(this));
}
initDatasource() {
return this.backendSrv.get('/api/datasources').then(res => {
var found = false;
for (let ds of res) {
if (ds.type === "grafana-testdata-datasource") {
found = true;
}
}
if (!found) {
var dsInstance = {
name: 'Grafana TestData',
type: 'grafana-testdata-datasource',
access: 'direct',
jsonData: {}
};
return this.backendSrv.post('/api/datasources', dsInstance);
}
return Promise.resolve();
});
}
}

View File

@ -0,0 +1,22 @@
<query-editor-row query-ctrl="ctrl" has-text-edit-mode="false">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword">Scenario</label>
<div class="gf-form-select-wrapper width-20">
<select class="gf-form-input width-20" ng-model="ctrl.target.scenario" ng-options="k as v.text for (k, v) in ctrl.scenarioDefs" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">With Options</label>
<input type="text" class="gf-form-input" placeholder="optional" ng-model="target.param1" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">Alias</label>
<input type="text" class="gf-form-input" placeholder="optional" ng-model="target.alias" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>

View File

@ -0,0 +1,27 @@
{
"type": "app",
"name": "Grafana TestData",
"id": "testdata",
"info": {
"description": "Grafana test data app",
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
"version": "1.0.5",
"updated": "2016-09-26"
},
"includes": [
{
"type": "dashboard",
"name": "TestData - Graph Last 1h",
"path": "dashboards/graph_last_1h.json"
}
],
"dependencies": {
"grafanaVersion": "4.x.x"
}
}

View File

@ -2,6 +2,7 @@
import kbn from 'app/core/utils/kbn';
import _ from 'lodash';
import moment from 'moment';
import TimeSeries from 'app/core/time_series2';
import {colors} from 'app/core/core';
@ -29,7 +30,9 @@ export class DataProcessor {
switch (this.panel.xaxis.mode) {
case 'series':
case 'time': {
return options.dataList.map(this.timeSeriesHandler.bind(this));
return options.dataList.map((item, index) => {
return this.timeSeriesHandler(item, index, options);
});
}
case 'field': {
return this.customHandler(firstItem);
@ -74,31 +77,24 @@ export class DataProcessor {
}
}
seriesHandler(seriesData, index, datapoints, alias) {
timeSeriesHandler(seriesData, index, options) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var colorIndex = index % colors.length;
var color = this.panel.aliasColors[alias] || colors[colorIndex];
var series = new TimeSeries({datapoints: datapoints, alias: alias, color: color, unit: seriesData.unit});
// if (datapoints && datapoints.length > 0) {
// var last = moment.utc(datapoints[datapoints.length - 1][1]);
// var from = moment.utc(this.range.from);
// if (last - from < -10000) {
// this.datapointsOutside = true;
// }
//
// this.datapointsCount += datapoints.length;
// this.panel.tooltip.msResolution = this.panel.tooltip.msResolution || series.isMsResolutionNeeded();
// }
return series;
if (datapoints && datapoints.length > 0) {
var last = datapoints[datapoints.length - 1][1];
var from = options.range.from;
if (last - from < -10000) {
series.isOutsideRange = true;
}
}
timeSeriesHandler(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
return this.seriesHandler(seriesData, index, datapoints, alias);
return series;
}
customHandler(dataItem) {
@ -126,21 +122,21 @@ export class DataProcessor {
return [];
}
tableHandler(seriesData, index) {
var xColumnIndex = Number(this.panel.xaxis.columnIndex);
var valueColumnIndex = Number(this.panel.xaxis.valueColumnIndex);
var datapoints = _.map(seriesData.rows, (row) => {
var value = valueColumnIndex ? row[valueColumnIndex] : _.last(row);
return [
value, // Y value
row[xColumnIndex] // X value
];
});
var alias = seriesData.columns[valueColumnIndex].text;
return this.seriesHandler(seriesData, index, datapoints, alias);
}
// tableHandler(seriesData, index) {
// var xColumnIndex = Number(this.panel.xaxis.columnIndex);
// var valueColumnIndex = Number(this.panel.xaxis.valueColumnIndex);
// var datapoints = _.map(seriesData.rows, (row) => {
// var value = valueColumnIndex ? row[valueColumnIndex] : _.last(row);
// return [
// value, // Y value
// row[xColumnIndex] // X value
// ];
// });
//
// var alias = seriesData.columns[valueColumnIndex].text;
//
// return this.seriesHandler(seriesData, index, datapoints, alias);
// }
// esRawDocHandler(seriesData, index) {
// let xField = this.panel.xaxis.esField;
@ -160,7 +156,7 @@ export class DataProcessor {
// var alias = valueField;
// return this.seriesHandler(seriesData, index, datapoints, alias);
// }
//
validateXAxisSeriesValue() {
switch (this.panel.xaxis.mode) {
case 'series': {

View File

@ -121,20 +121,20 @@ function ($, _) {
var seriesList = getSeriesFn();
var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat;
if (panel.tooltip.msResolution) {
tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
} else {
tooltipFormat = 'YYYY-MM-DD HH:mm:ss';
}
if (dashboard.sharedCrosshair) {
ctrl.publishAppEvent('setCrosshair', { pos: pos, scope: scope });
ctrl.publishAppEvent('setCrosshair', {pos: pos, scope: scope});
}
if (seriesList.length === 0) {
return;
}
if (seriesList[0].hasMsResolution) {
tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
} else {
tooltipFormat = 'YYYY-MM-DD HH:mm:ss';
}
if (panel.tooltip.shared) {
plot.unhighlight();

View File

@ -25,7 +25,6 @@ class GraphCtrl extends MetricsPanelCtrl {
annotationsPromise: any;
datapointsCount: number;
datapointsOutside: boolean;
datapointsWarning: boolean;
colors: any = [];
subTabIndex: number;
processor: DataProcessor;
@ -172,13 +171,20 @@ class GraphCtrl extends MetricsPanelCtrl {
}
onDataReceived(dataList) {
this.datapointsWarning = false;
this.datapointsCount = 0;
this.datapointsOutside = false;
this.dataList = dataList;
this.seriesList = this.processor.getSeriesList({dataList: dataList, range: this.range});
this.datapointsWarning = this.datapointsCount === 0 || this.datapointsOutside;
this.datapointsCount = this.seriesList.reduce((prev, series) => {
return prev + series.datapoints.length;
}, 0);
this.datapointsOutside = false;
for (let series of this.seriesList) {
if (series.isOutsideRange) {
this.datapointsOutside = true;
}
}
this.annotationsPromise.then(annotations => {
this.loading = false;

View File

@ -3,6 +3,7 @@
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
import angular from 'angular';
import moment from 'moment';
import {GraphCtrl} from '../module';
import helpers from '../../../../../test/specs/helpers';
@ -19,64 +20,53 @@ describe('GraphCtrl', function() {
ctx.ctrl.updateTimeRange();
});
describe.skip('msResolution with second resolution timestamps', function() {
describe('when time series are outside range', function() {
beforeEach(function() {
var data = [
{ target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
{ target: 'test.cpu2', datapoints: [[55, 1236547890], [90, 1234456709]]}
{target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
];
ctx.ctrl.panel.tooltip.msResolution = false;
ctx.ctrl.range = {from: moment().valueOf(), to: moment().valueOf()};
ctx.ctrl.onDataReceived(data);
});
it('should not show millisecond resolution tooltip', function() {
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(false);
it('should set datapointsOutside', function() {
expect(ctx.ctrl.datapointsOutside).to.be(true);
});
});
describe.skip('msResolution with millisecond resolution timestamps', function() {
describe('when time series are inside range', function() {
beforeEach(function() {
var range = {
from: moment().subtract(1, 'days').valueOf(),
to: moment().valueOf()
};
var data = [
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
{ target: 'test.cpu2', datapoints: [[55, 1236547890001], [90, 1234456709000]]}
{target: 'test.cpu1', datapoints: [[45, range.from + 1000], [60, range.from + 10000]]},
];
ctx.ctrl.panel.tooltip.msResolution = false;
ctx.ctrl.range = range;
ctx.ctrl.onDataReceived(data);
});
it('should show millisecond resolution tooltip', function() {
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(true);
it('should set datapointsOutside', function() {
expect(ctx.ctrl.datapointsOutside).to.be(false);
});
});
describe.skip('msResolution with millisecond resolution timestamps but with trailing zeroes', function() {
describe('datapointsCount given 2 series', function() {
beforeEach(function() {
var data = [
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
{ target: 'test.cpu2', datapoints: [[55, 1236547890000], [90, 1234456709000]]}
{target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
{target: 'test.cpu2', datapoints: [[45, 1234567890]]},
];
ctx.ctrl.panel.tooltip.msResolution = false;
ctx.ctrl.onDataReceived(data);
});
it('should not show millisecond resolution tooltip', function() {
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(false);
});
});
describe.skip('msResolution with millisecond resolution timestamps in one of the series', function() {
beforeEach(function() {
var data = [
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
{ target: 'test.cpu2', datapoints: [[55, 1236547890010], [90, 1234456709000]]},
{ target: 'test.cpu3', datapoints: [[65, 1236547890000], [120, 1234456709000]]}
];
ctx.ctrl.panel.tooltip.msResolution = false;
ctx.ctrl.onDataReceived(data);
});
it('should show millisecond resolution tooltip', function() {
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(true);
it('should set datapointsCount to sum of datapoints', function() {
expect(ctx.ctrl.datapointsCount).to.be(3);
});
});

View File

@ -2,11 +2,14 @@ var template = `
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': ctrl.panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">
<div class="datapoints-warning" ng-show="ctrl.datapointsCount===0">
<span class="small" >
No datapoints <tip>No datapoints returned from metric query</tip>
</span>
<span class="small" ng-show="datapointsOutside">
</div>
<div class="datapoints-warning" ng-show="ctrl.datapointsOutside">
<span class="small">
Datapoints outside time range
<tip>Can be caused by timezone mismatch between browser and graphite server</tip>
</span>

View File

@ -7,7 +7,7 @@
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
},
"logos": {
"small": "img/icn-dashlist-panel.svg",
"large": "img/icn-dashlist-panel.svg"

View File

@ -56,6 +56,38 @@ define([
});
});
describe('When checking if ms resolution is needed', function() {
describe('msResolution with second resolution timestamps', function() {
beforeEach(function() {
series = new TimeSeries({datapoints: [[45, 1234567890], [60, 1234567899]]});
});
it('should set hasMsResolution to false', function() {
expect(series.hasMsResolution).to.be(false);
});
});
describe('msResolution with millisecond resolution timestamps', function() {
beforeEach(function() {
series = new TimeSeries({datapoints: [[55, 1236547890001], [90, 1234456709000]]});
});
it('should show millisecond resolution tooltip', function() {
expect(series.hasMsResolution).to.be(true);
});
});
describe('msResolution with millisecond resolution timestamps but with trailing zeroes', function() {
beforeEach(function() {
series = new TimeSeries({datapoints: [[45, 1234567890000], [60, 1234567899000]]});
});
it('should not show millisecond resolution tooltip', function() {
expect(series.hasMsResolution).to.be(false);
});
});
});
describe('can detect if series contains ms precision', function() {
var fakedata;

View File

@ -132,38 +132,40 @@ define([
describe('calculateInterval', function() {
it('1h 100 resultion', function() {
var range = { from: dateMath.parse('now-1h'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 100, null);
expect(str).to.be('30s');
var res = kbn.calculateInterval(range, 100, null);
expect(res.interval).to.be('30s');
});
it('10m 1600 resolution', function() {
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 1600, null);
expect(str).to.be('500ms');
var res = kbn.calculateInterval(range, 1600, null);
expect(res.interval).to.be('500ms');
expect(res.intervalMs).to.be(500);
});
it('fixed user interval', function() {
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 1600, '10s');
expect(str).to.be('10s');
var res = kbn.calculateInterval(range, 1600, '10s');
expect(res.interval).to.be('10s');
expect(res.intervalMs).to.be(10000);
});
it('short time range and user low limit', function() {
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 1600, '>10s');
expect(str).to.be('10s');
var res = kbn.calculateInterval(range, 1600, '>10s');
expect(res.interval).to.be('10s');
});
it('large time range and user low limit', function() {
var range = { from: dateMath.parse('now-14d'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 1000, '>10s');
expect(str).to.be('20m');
var range = {from: dateMath.parse('now-14d'), to: dateMath.parse('now')};
var res = kbn.calculateInterval(range, 1000, '>10s');
expect(res.interval).to.be('20m');
});
it('10s 900 resolution and user low limit in ms', function() {
var range = { from: dateMath.parse('now-10s'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 900, '>15ms');
expect(str).to.be('15ms');
var res = kbn.calculateInterval(range, 900, '>15ms');
expect(res.interval).to.be('15ms');
});
});