mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 14:52:46 +08:00
feat(testdata): worked on testdata app
This commit is contained in:
@ -97,12 +97,7 @@ func (slice DataSourceList) Swap(i, j int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MetricQueryResultDto struct {
|
type MetricQueryResultDto struct {
|
||||||
Data []MetricQueryResultDataDto `json:"data"`
|
Data []interface{} `json:"data"`
|
||||||
}
|
|
||||||
|
|
||||||
type MetricQueryResultDataDto struct {
|
|
||||||
Target string `json:"target"`
|
|
||||||
DataPoints [][2]float64 `json:"datapoints"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserStars struct {
|
type UserStars struct {
|
||||||
|
@ -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{Divider: true})
|
||||||
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "fa fa-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
|
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "fa fa-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
|
||||||
}
|
}
|
||||||
|
@ -2,39 +2,49 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTestMetrics(c *middleware.Context) Response {
|
func GetTestMetrics(c *middleware.Context) Response {
|
||||||
from := c.QueryInt64("from")
|
|
||||||
to := c.QueryInt64("to")
|
timeRange := tsdb.NewTimeRange(c.Query("from"), c.Query("to"))
|
||||||
maxDataPoints := c.QueryInt64("maxDataPoints")
|
|
||||||
stepInSeconds := (to - from) / maxDataPoints
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := tsdb.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return ApiError(500, "Metric request error", err)
|
||||||
|
}
|
||||||
|
|
||||||
result := dtos.MetricQueryResultDto{}
|
result := dtos.MetricQueryResultDto{}
|
||||||
result.Data = make([]dtos.MetricQueryResultDataDto, 1)
|
|
||||||
|
|
||||||
for seriesIndex := range result.Data {
|
for _, v := range resp.Results {
|
||||||
points := make([][2]float64, maxDataPoints)
|
if v.Error != nil {
|
||||||
walker := rand.Float64() * 100
|
return ApiError(500, "tsdb.HandleRequest() response error", v.Error)
|
||||||
time := from
|
|
||||||
|
|
||||||
for i := range points {
|
|
||||||
points[i][0] = walker
|
|
||||||
points[i][1] = float64(time)
|
|
||||||
walker += rand.Float64() - 0.5
|
|
||||||
time += stepInSeconds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Data[seriesIndex].Target = "test-series-" + strconv.Itoa(seriesIndex)
|
for _, series := range v.Series {
|
||||||
result.Data[seriesIndex].DataPoints = points
|
result.Data = append(result.Data, series)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Json(200, &result)
|
return Json(200, &result)
|
||||||
|
@ -43,7 +43,12 @@ func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
|
|||||||
appSubPath := strings.Replace(fp.PluginDir, app.PluginDir, "", 1)
|
appSubPath := strings.Replace(fp.PluginDir, app.PluginDir, "", 1)
|
||||||
fp.IncludedInAppId = app.Id
|
fp.IncludedInAppId = app.Id
|
||||||
fp.BaseUrl = app.BaseUrl
|
fp.BaseUrl = app.BaseUrl
|
||||||
fp.Module = util.JoinUrlFragments("plugins/"+app.Id, appSubPath) + "/module"
|
|
||||||
|
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() {
|
func (fp *FrontendPluginBase) handleModuleDefaults() {
|
||||||
|
@ -34,8 +34,8 @@ type AlertQuery struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *QueryCondition) Eval(context *alerting.EvalContext) {
|
func (c *QueryCondition) Eval(context *alerting.EvalContext) {
|
||||||
timerange := tsdb.NewTimerange(c.Query.From, c.Query.To)
|
timeRange := tsdb.NewTimeRange(c.Query.From, c.Query.To)
|
||||||
seriesList, err := c.executeQuery(context, timerange)
|
seriesList, err := c.executeQuery(context, timeRange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error = err
|
context.Error = err
|
||||||
return
|
return
|
||||||
@ -69,7 +69,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) {
|
|||||||
context.Firing = len(context.EvalMatches) > 0
|
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{
|
getDsInfo := &m.GetDataSourceByIdQuery{
|
||||||
Id: c.Query.DatasourceId,
|
Id: c.Query.DatasourceId,
|
||||||
OrgId: context.Rule.OrgId,
|
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")
|
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)
|
result := make(tsdb.TimeSeriesSlice, 0)
|
||||||
|
|
||||||
resp, err := c.HandleRequest(req)
|
resp, err := c.HandleRequest(req)
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
_ "github.com/grafana/grafana/pkg/tsdb/graphite"
|
_ "github.com/grafana/grafana/pkg/tsdb/graphite"
|
||||||
_ "github.com/grafana/grafana/pkg/tsdb/prometheus"
|
_ "github.com/grafana/grafana/pkg/tsdb/prometheus"
|
||||||
|
_ "github.com/grafana/grafana/pkg/tsdb/testdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
var engine *alerting.Engine
|
var engine *alerting.Engine
|
||||||
|
@ -3,21 +3,22 @@ package tsdb
|
|||||||
import "github.com/grafana/grafana/pkg/components/simplejson"
|
import "github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
RefId string
|
RefId string
|
||||||
Query string
|
Query string
|
||||||
Model *simplejson.Json
|
Model *simplejson.Json
|
||||||
Depends []string
|
Depends []string
|
||||||
DataSource *DataSourceInfo
|
DataSource *DataSourceInfo
|
||||||
Results []*TimeSeries
|
Results []*TimeSeries
|
||||||
Exclude bool
|
Exclude bool
|
||||||
|
MaxDataPoints int64
|
||||||
|
IntervalMs int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type QuerySlice []*Query
|
type QuerySlice []*Query
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
TimeRange TimeRange
|
TimeRange TimeRange
|
||||||
MaxDataPoints int
|
Queries QuerySlice
|
||||||
Queries QuerySlice
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
54
pkg/tsdb/testdata/testdata.go
vendored
Normal file
54
pkg/tsdb/testdata/testdata.go
vendored
Normal 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, ×tamp})
|
||||||
|
|
||||||
|
walker += rand.Float64() - 0.5
|
||||||
|
time += stepInSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
series.Points = points
|
||||||
|
queryRes.Series = append(queryRes.Series, series)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.QueryResults["A"] = queryRes
|
||||||
|
return result
|
||||||
|
}
|
@ -2,11 +2,12 @@ package tsdb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTimerange(from, to string) TimeRange {
|
func NewTimeRange(from, to string) TimeRange {
|
||||||
return TimeRange{
|
return TimeRange{
|
||||||
From: from,
|
From: from,
|
||||||
To: to,
|
To: to,
|
||||||
@ -21,6 +22,10 @@ type TimeRange struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tr TimeRange) FromTime() (time.Time, error) {
|
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)
|
fromRaw := strings.Replace(tr.From, "now-", "", 1)
|
||||||
|
|
||||||
diff, err := time.ParseDuration("-" + fromRaw)
|
diff, err := time.ParseDuration("-" + fromRaw)
|
||||||
@ -45,5 +50,9 @@ func (tr TimeRange) ToTime() (time.Time, error) {
|
|||||||
return tr.Now.Add(diff), nil
|
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)
|
return time.Time{}, fmt.Errorf("cannot parse to value %s", tr.To)
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
Convey("Cannot parse asdf", func() {
|
||||||
var err error
|
var err error
|
||||||
tr := TimeRange{
|
tr := TimeRange{
|
||||||
|
@ -31,6 +31,8 @@ export default class TimeSeries {
|
|||||||
allIsZero: boolean;
|
allIsZero: boolean;
|
||||||
decimals: number;
|
decimals: number;
|
||||||
scaledDecimals: number;
|
scaledDecimals: number;
|
||||||
|
hasMsResolution: boolean;
|
||||||
|
isOutsideRange: boolean;
|
||||||
|
|
||||||
lines: any;
|
lines: any;
|
||||||
bars: any;
|
bars: any;
|
||||||
@ -54,6 +56,7 @@ export default class TimeSeries {
|
|||||||
this.stats = {};
|
this.stats = {};
|
||||||
this.legend = true;
|
this.legend = true;
|
||||||
this.unit = opts.unit;
|
this.unit = opts.unit;
|
||||||
|
this.hasMsResolution = this.isMsResolutionNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
applySeriesOverrides(overrides) {
|
applySeriesOverrides(overrides) {
|
||||||
|
@ -174,7 +174,10 @@ function($, _, moment) {
|
|||||||
lowLimitMs = kbn.interval_to_ms(lowLimitInterval);
|
lowLimitMs = kbn.interval_to_ms(lowLimitInterval);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return userInterval;
|
return {
|
||||||
|
intervalMs: kbn.interval_to_ms(userInterval),
|
||||||
|
interval: userInterval,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +186,10 @@ function($, _, moment) {
|
|||||||
intervalMs = lowLimitMs;
|
intervalMs = lowLimitMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
return kbn.secondsToHms(intervalMs / 1000);
|
return {
|
||||||
|
intervalMs: intervalMs,
|
||||||
|
interval: kbn.secondsToHms(intervalMs / 1000),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
kbn.describe_interval = function (string) {
|
kbn.describe_interval = function (string) {
|
||||||
|
@ -25,6 +25,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|||||||
range: any;
|
range: any;
|
||||||
rangeRaw: any;
|
rangeRaw: any;
|
||||||
interval: any;
|
interval: any;
|
||||||
|
intervalMs: any;
|
||||||
resolution: any;
|
resolution: any;
|
||||||
timeInfo: any;
|
timeInfo: any;
|
||||||
skipDataOnInit: boolean;
|
skipDataOnInit: boolean;
|
||||||
@ -123,11 +124,22 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|||||||
this.resolution = Math.ceil($(window).width() * (this.panel.span / 12));
|
this.resolution = Math.ceil($(window).width() * (this.panel.span / 12));
|
||||||
}
|
}
|
||||||
|
|
||||||
var panelInterval = this.panel.interval;
|
this.calculateInterval();
|
||||||
var datasourceInterval = (this.datasource || {}).interval;
|
|
||||||
this.interval = kbn.calculateInterval(this.range, this.resolution, panelInterval || datasourceInterval);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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() {
|
applyPanelTimeOverrides() {
|
||||||
this.timeInfo = '';
|
this.timeInfo = '';
|
||||||
|
|
||||||
@ -183,6 +195,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|||||||
range: this.range,
|
range: this.range,
|
||||||
rangeRaw: this.rangeRaw,
|
rangeRaw: this.rangeRaw,
|
||||||
interval: this.interval,
|
interval: this.interval,
|
||||||
|
intervalMs: this.intervalMs,
|
||||||
targets: this.panel.targets,
|
targets: this.panel.targets,
|
||||||
format: this.panel.renderer === 'png' ? 'png' : 'json',
|
format: this.panel.renderer === 'png' ? 'png' : 'json',
|
||||||
maxDataPoints: this.resolution,
|
maxDataPoints: this.resolution,
|
||||||
|
@ -54,8 +54,8 @@ export class IntervalVariable implements Variable {
|
|||||||
this.options.unshift({ text: 'auto', value: '$__auto_interval' });
|
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));
|
var res = kbn.calculateInterval(this.timeSrv.timeRange(), this.auto_count, (this.auto_min ? ">"+this.auto_min : null));
|
||||||
this.templateSrv.setGrafanaVariable('$__auto_interval', interval);
|
this.templateSrv.setGrafanaVariable('$__auto_interval', res.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOptions() {
|
updateOptions() {
|
||||||
|
5
public/app/plugins/app/testdata/dashboards/graph_last_1h.json
vendored
Normal file
5
public/app/plugins/app/testdata/dashboards/graph_last_1h.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "TestData - Graph Panel Last 1h",
|
||||||
|
"tags": ["testdata"],
|
||||||
|
"revision": 1
|
||||||
|
}
|
45
public/app/plugins/app/testdata/datasource/datasource.ts
vendored
Normal file
45
public/app/plugins/app/testdata/datasource/datasource.ts
vendored
Normal 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};
|
22
public/app/plugins/app/testdata/datasource/module.ts
vendored
Normal file
22
public/app/plugins/app/testdata/datasource/module.ts
vendored
Normal 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,
|
||||||
|
};
|
||||||
|
|
19
public/app/plugins/app/testdata/datasource/plugin.json
vendored
Normal file
19
public/app/plugins/app/testdata/datasource/plugin.json
vendored
Normal 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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
public/app/plugins/app/testdata/datasource/query_ctrl.ts
vendored
Normal file
24
public/app/plugins/app/testdata/datasource/query_ctrl.ts
vendored
Normal 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'},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
36
public/app/plugins/app/testdata/module.ts
vendored
Normal file
36
public/app/plugins/app/testdata/module.ts
vendored
Normal 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
22
public/app/plugins/app/testdata/partials/query.editor.html
vendored
Normal file
22
public/app/plugins/app/testdata/partials/query.editor.html
vendored
Normal 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>
|
||||||
|
|
27
public/app/plugins/app/testdata/plugin.json
vendored
Normal file
27
public/app/plugins/app/testdata/plugin.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
import {colors} from 'app/core/core';
|
import {colors} from 'app/core/core';
|
||||||
|
|
||||||
@ -28,8 +29,10 @@ export class DataProcessor {
|
|||||||
|
|
||||||
switch (this.panel.xaxis.mode) {
|
switch (this.panel.xaxis.mode) {
|
||||||
case 'series':
|
case 'series':
|
||||||
case 'time': {
|
case 'time': {
|
||||||
return options.dataList.map(this.timeSeriesHandler.bind(this));
|
return options.dataList.map((item, index) => {
|
||||||
|
return this.timeSeriesHandler(item, index, options);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
case 'field': {
|
case 'field': {
|
||||||
return this.customHandler(firstItem);
|
return this.customHandler(firstItem);
|
||||||
@ -74,33 +77,26 @@ 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 colorIndex = index % colors.length;
|
||||||
var color = this.panel.aliasColors[alias] || colors[colorIndex];
|
var color = this.panel.aliasColors[alias] || colors[colorIndex];
|
||||||
|
|
||||||
var series = new TimeSeries({datapoints: datapoints, alias: alias, color: color, unit: seriesData.unit});
|
var series = new TimeSeries({datapoints: datapoints, alias: alias, color: color, unit: seriesData.unit});
|
||||||
|
|
||||||
// if (datapoints && datapoints.length > 0) {
|
if (datapoints && datapoints.length > 0) {
|
||||||
// var last = moment.utc(datapoints[datapoints.length - 1][1]);
|
var last = datapoints[datapoints.length - 1][1];
|
||||||
// var from = moment.utc(this.range.from);
|
var from = options.range.from;
|
||||||
// if (last - from < -10000) {
|
if (last - from < -10000) {
|
||||||
// this.datapointsOutside = true;
|
series.isOutsideRange = true;
|
||||||
// }
|
}
|
||||||
//
|
}
|
||||||
// this.datapointsCount += datapoints.length;
|
|
||||||
// this.panel.tooltip.msResolution = this.panel.tooltip.msResolution || series.isMsResolutionNeeded();
|
|
||||||
// }
|
|
||||||
|
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeSeriesHandler(seriesData, index) {
|
|
||||||
var datapoints = seriesData.datapoints;
|
|
||||||
var alias = seriesData.target;
|
|
||||||
|
|
||||||
return this.seriesHandler(seriesData, index, datapoints, alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
customHandler(dataItem) {
|
customHandler(dataItem) {
|
||||||
console.log('custom', dataItem);
|
console.log('custom', dataItem);
|
||||||
let nameField = this.panel.xaxis.name;
|
let nameField = this.panel.xaxis.name;
|
||||||
@ -126,21 +122,21 @@ export class DataProcessor {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
tableHandler(seriesData, index) {
|
// tableHandler(seriesData, index) {
|
||||||
var xColumnIndex = Number(this.panel.xaxis.columnIndex);
|
// var xColumnIndex = Number(this.panel.xaxis.columnIndex);
|
||||||
var valueColumnIndex = Number(this.panel.xaxis.valueColumnIndex);
|
// var valueColumnIndex = Number(this.panel.xaxis.valueColumnIndex);
|
||||||
var datapoints = _.map(seriesData.rows, (row) => {
|
// var datapoints = _.map(seriesData.rows, (row) => {
|
||||||
var value = valueColumnIndex ? row[valueColumnIndex] : _.last(row);
|
// var value = valueColumnIndex ? row[valueColumnIndex] : _.last(row);
|
||||||
return [
|
// return [
|
||||||
value, // Y value
|
// value, // Y value
|
||||||
row[xColumnIndex] // X value
|
// row[xColumnIndex] // X value
|
||||||
];
|
// ];
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
var alias = seriesData.columns[valueColumnIndex].text;
|
// var alias = seriesData.columns[valueColumnIndex].text;
|
||||||
|
//
|
||||||
return this.seriesHandler(seriesData, index, datapoints, alias);
|
// return this.seriesHandler(seriesData, index, datapoints, alias);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// esRawDocHandler(seriesData, index) {
|
// esRawDocHandler(seriesData, index) {
|
||||||
// let xField = this.panel.xaxis.esField;
|
// let xField = this.panel.xaxis.esField;
|
||||||
@ -160,7 +156,7 @@ export class DataProcessor {
|
|||||||
// var alias = valueField;
|
// var alias = valueField;
|
||||||
// return this.seriesHandler(seriesData, index, datapoints, alias);
|
// return this.seriesHandler(seriesData, index, datapoints, alias);
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
validateXAxisSeriesValue() {
|
validateXAxisSeriesValue() {
|
||||||
switch (this.panel.xaxis.mode) {
|
switch (this.panel.xaxis.mode) {
|
||||||
case 'series': {
|
case 'series': {
|
||||||
|
@ -121,20 +121,20 @@ function ($, _) {
|
|||||||
var seriesList = getSeriesFn();
|
var seriesList = getSeriesFn();
|
||||||
var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat;
|
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) {
|
if (dashboard.sharedCrosshair) {
|
||||||
ctrl.publishAppEvent('setCrosshair', { pos: pos, scope: scope });
|
ctrl.publishAppEvent('setCrosshair', {pos: pos, scope: scope});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seriesList.length === 0) {
|
if (seriesList.length === 0) {
|
||||||
return;
|
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) {
|
if (panel.tooltip.shared) {
|
||||||
plot.unhighlight();
|
plot.unhighlight();
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ class GraphCtrl extends MetricsPanelCtrl {
|
|||||||
annotationsPromise: any;
|
annotationsPromise: any;
|
||||||
datapointsCount: number;
|
datapointsCount: number;
|
||||||
datapointsOutside: boolean;
|
datapointsOutside: boolean;
|
||||||
datapointsWarning: boolean;
|
|
||||||
colors: any = [];
|
colors: any = [];
|
||||||
subTabIndex: number;
|
subTabIndex: number;
|
||||||
processor: DataProcessor;
|
processor: DataProcessor;
|
||||||
@ -172,13 +171,20 @@ class GraphCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDataReceived(dataList) {
|
onDataReceived(dataList) {
|
||||||
this.datapointsWarning = false;
|
|
||||||
this.datapointsCount = 0;
|
|
||||||
this.datapointsOutside = false;
|
|
||||||
|
|
||||||
this.dataList = dataList;
|
this.dataList = dataList;
|
||||||
this.seriesList = this.processor.getSeriesList({dataList: dataList, range: this.range});
|
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.annotationsPromise.then(annotations => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
|
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
|
||||||
|
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
import moment from 'moment';
|
||||||
import {GraphCtrl} from '../module';
|
import {GraphCtrl} from '../module';
|
||||||
import helpers from '../../../../../test/specs/helpers';
|
import helpers from '../../../../../test/specs/helpers';
|
||||||
|
|
||||||
@ -19,64 +20,53 @@ describe('GraphCtrl', function() {
|
|||||||
ctx.ctrl.updateTimeRange();
|
ctx.ctrl.updateTimeRange();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.skip('msResolution with second resolution timestamps', function() {
|
describe('when time series are outside range', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
var data = [
|
var data = [
|
||||||
{ target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
|
{target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
|
||||||
{ target: 'test.cpu2', datapoints: [[55, 1236547890], [90, 1234456709]]}
|
|
||||||
];
|
];
|
||||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
|
||||||
|
ctx.ctrl.range = {from: moment().valueOf(), to: moment().valueOf()};
|
||||||
ctx.ctrl.onDataReceived(data);
|
ctx.ctrl.onDataReceived(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show millisecond resolution tooltip', function() {
|
it('should set datapointsOutside', function() {
|
||||||
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(false);
|
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() {
|
beforeEach(function() {
|
||||||
|
var range = {
|
||||||
|
from: moment().subtract(1, 'days').valueOf(),
|
||||||
|
to: moment().valueOf()
|
||||||
|
};
|
||||||
|
|
||||||
var data = [
|
var data = [
|
||||||
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
|
{target: 'test.cpu1', datapoints: [[45, range.from + 1000], [60, range.from + 10000]]},
|
||||||
{ target: 'test.cpu2', datapoints: [[55, 1236547890001], [90, 1234456709000]]}
|
|
||||||
];
|
];
|
||||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
|
||||||
|
ctx.ctrl.range = range;
|
||||||
ctx.ctrl.onDataReceived(data);
|
ctx.ctrl.onDataReceived(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show millisecond resolution tooltip', function() {
|
it('should set datapointsOutside', function() {
|
||||||
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(true);
|
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() {
|
beforeEach(function() {
|
||||||
var data = [
|
var data = [
|
||||||
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
|
{target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
|
||||||
{ target: 'test.cpu2', datapoints: [[55, 1236547890000], [90, 1234456709000]]}
|
{target: 'test.cpu2', datapoints: [[45, 1234567890]]},
|
||||||
];
|
];
|
||||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
|
||||||
ctx.ctrl.onDataReceived(data);
|
ctx.ctrl.onDataReceived(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show millisecond resolution tooltip', function() {
|
it('should set datapointsCount to sum of datapoints', function() {
|
||||||
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(false);
|
expect(ctx.ctrl.datapointsCount).to.be(3);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,11 +2,14 @@ var template = `
|
|||||||
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': ctrl.panel.legend.rightSide}">
|
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': ctrl.panel.legend.rightSide}">
|
||||||
<div class="graph-canvas-wrapper">
|
<div class="graph-canvas-wrapper">
|
||||||
|
|
||||||
<div ng-if="datapointsWarning" class="datapoints-warning">
|
<div class="datapoints-warning" ng-show="ctrl.datapointsCount===0">
|
||||||
<span class="small" ng-show="!datapointsCount">
|
<span class="small" >
|
||||||
No datapoints <tip>No datapoints returned from metric query</tip>
|
No datapoints <tip>No datapoints returned from metric query</tip>
|
||||||
</span>
|
</span>
|
||||||
<span class="small" ng-show="datapointsOutside">
|
</div>
|
||||||
|
|
||||||
|
<div class="datapoints-warning" ng-show="ctrl.datapointsOutside">
|
||||||
|
<span class="small">
|
||||||
Datapoints outside time range
|
Datapoints outside time range
|
||||||
<tip>Can be caused by timezone mismatch between browser and graphite server</tip>
|
<tip>Can be caused by timezone mismatch between browser and graphite server</tip>
|
||||||
</span>
|
</span>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"author": {
|
"author": {
|
||||||
"name": "Grafana Project",
|
"name": "Grafana Project",
|
||||||
"url": "http://grafana.org"
|
"url": "http://grafana.org"
|
||||||
},
|
},
|
||||||
"logos": {
|
"logos": {
|
||||||
"small": "img/icn-dashlist-panel.svg",
|
"small": "img/icn-dashlist-panel.svg",
|
||||||
"large": "img/icn-dashlist-panel.svg"
|
"large": "img/icn-dashlist-panel.svg"
|
||||||
|
@ -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() {
|
describe('can detect if series contains ms precision', function() {
|
||||||
var fakedata;
|
var fakedata;
|
||||||
|
|
||||||
|
@ -132,62 +132,64 @@ define([
|
|||||||
describe('calculateInterval', function() {
|
describe('calculateInterval', function() {
|
||||||
it('1h 100 resultion', function() {
|
it('1h 100 resultion', function() {
|
||||||
var range = { from: dateMath.parse('now-1h'), to: dateMath.parse('now') };
|
var range = { from: dateMath.parse('now-1h'), to: dateMath.parse('now') };
|
||||||
var str = kbn.calculateInterval(range, 100, null);
|
var res = kbn.calculateInterval(range, 100, null);
|
||||||
expect(str).to.be('30s');
|
expect(res.interval).to.be('30s');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('10m 1600 resolution', function() {
|
it('10m 1600 resolution', function() {
|
||||||
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
||||||
var str = kbn.calculateInterval(range, 1600, null);
|
var res = kbn.calculateInterval(range, 1600, null);
|
||||||
expect(str).to.be('500ms');
|
expect(res.interval).to.be('500ms');
|
||||||
|
expect(res.intervalMs).to.be(500);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fixed user interval', function() {
|
it('fixed user interval', function() {
|
||||||
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
||||||
var str = kbn.calculateInterval(range, 1600, '10s');
|
var res = kbn.calculateInterval(range, 1600, '10s');
|
||||||
expect(str).to.be('10s');
|
expect(res.interval).to.be('10s');
|
||||||
|
expect(res.intervalMs).to.be(10000);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('short time range and user low limit', function() {
|
it('short time range and user low limit', function() {
|
||||||
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
||||||
var str = kbn.calculateInterval(range, 1600, '>10s');
|
var res = kbn.calculateInterval(range, 1600, '>10s');
|
||||||
expect(str).to.be('10s');
|
expect(res.interval).to.be('10s');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('large time range and user low limit', function() {
|
it('large time range and user low limit', function() {
|
||||||
var range = { from: dateMath.parse('now-14d'), to: dateMath.parse('now') };
|
var range = {from: dateMath.parse('now-14d'), to: dateMath.parse('now')};
|
||||||
var str = kbn.calculateInterval(range, 1000, '>10s');
|
var res = kbn.calculateInterval(range, 1000, '>10s');
|
||||||
expect(str).to.be('20m');
|
expect(res.interval).to.be('20m');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('10s 900 resolution and user low limit in ms', function() {
|
it('10s 900 resolution and user low limit in ms', function() {
|
||||||
var range = { from: dateMath.parse('now-10s'), to: dateMath.parse('now') };
|
var range = { from: dateMath.parse('now-10s'), to: dateMath.parse('now') };
|
||||||
var str = kbn.calculateInterval(range, 900, '>15ms');
|
var res = kbn.calculateInterval(range, 900, '>15ms');
|
||||||
expect(str).to.be('15ms');
|
expect(res.interval).to.be('15ms');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hex', function() {
|
describe('hex', function() {
|
||||||
it('positive integer', function() {
|
it('positive integer', function() {
|
||||||
var str = kbn.valueFormats.hex(100, 0);
|
var str = kbn.valueFormats.hex(100, 0);
|
||||||
expect(str).to.be('64');
|
expect(str).to.be('64');
|
||||||
});
|
});
|
||||||
it('negative integer', function() {
|
it('negative integer', function() {
|
||||||
var str = kbn.valueFormats.hex(-100, 0);
|
var str = kbn.valueFormats.hex(-100, 0);
|
||||||
expect(str).to.be('-64');
|
expect(str).to.be('-64');
|
||||||
});
|
});
|
||||||
it('null', function() {
|
it('null', function() {
|
||||||
var str = kbn.valueFormats.hex(null, 0);
|
var str = kbn.valueFormats.hex(null, 0);
|
||||||
expect(str).to.be('');
|
expect(str).to.be('');
|
||||||
});
|
});
|
||||||
it('positive float', function() {
|
it('positive float', function() {
|
||||||
var str = kbn.valueFormats.hex(50.52, 1);
|
var str = kbn.valueFormats.hex(50.52, 1);
|
||||||
expect(str).to.be('32.8');
|
expect(str).to.be('32.8');
|
||||||
});
|
});
|
||||||
it('negative float', function() {
|
it('negative float', function() {
|
||||||
var str = kbn.valueFormats.hex(-50.333, 2);
|
var str = kbn.valueFormats.hex(-50.333, 2);
|
||||||
expect(str).to.be('-32.547AE147AE14');
|
expect(str).to.be('-32.547AE147AE14');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hex 0x', function() {
|
describe('hex 0x', function() {
|
||||||
|
Reference in New Issue
Block a user