mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Introduce TSDB service (#31520)
* Introduce TSDB service Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Erik Sundell <erik.sundell87@gmail.com> Co-authored-by: Will Browne <will.browne@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.org> Co-authored-by: Will Browne <wbrowne@users.noreply.github.com> Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
This commit is contained in:
@@ -6,7 +6,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus"
|
||||
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
|
||||
|
||||
gocontext "context"
|
||||
|
||||
@@ -16,7 +18,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
@@ -29,12 +30,11 @@ func init() {
|
||||
// QueryCondition is responsible for issue and query, reduce the
|
||||
// timeseries into single values and evaluate if they are firing or not.
|
||||
type QueryCondition struct {
|
||||
Index int
|
||||
Query AlertQuery
|
||||
Reducer *queryReducer
|
||||
Evaluator AlertEvaluator
|
||||
Operator string
|
||||
HandleRequest tsdb.HandleRequestFunc
|
||||
Index int
|
||||
Query AlertQuery
|
||||
Reducer *queryReducer
|
||||
Evaluator AlertEvaluator
|
||||
Operator string
|
||||
}
|
||||
|
||||
// AlertQuery contains information about what datasource a query
|
||||
@@ -47,10 +47,10 @@ type AlertQuery struct {
|
||||
}
|
||||
|
||||
// Eval evaluates the `QueryCondition`.
|
||||
func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.ConditionResult, error) {
|
||||
timeRange := tsdb.NewTimeRange(c.Query.From, c.Query.To)
|
||||
func (c *QueryCondition) Eval(context *alerting.EvalContext, requestHandler tsdbifaces.RequestHandler) (*alerting.ConditionResult, error) {
|
||||
timeRange := plugins.NewDataTimeRange(c.Query.From, c.Query.To)
|
||||
|
||||
seriesList, err := c.executeQuery(context, timeRange)
|
||||
seriesList, err := c.executeQuery(context, timeRange, requestHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -109,7 +109,8 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) {
|
||||
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange plugins.DataTimeRange,
|
||||
requestHandler tsdbifaces.RequestHandler) (plugins.DataTimeSeriesSlice, error) {
|
||||
getDsInfo := &models.GetDataSourceQuery{
|
||||
Id: c.Query.DatasourceID,
|
||||
OrgId: context.Rule.OrgID,
|
||||
@@ -125,7 +126,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
}
|
||||
|
||||
req := c.getRequestForAlertRule(getDsInfo.Result, timeRange, context.IsDebug)
|
||||
result := make(tsdb.TimeSeriesSlice, 0)
|
||||
result := make(plugins.DataTimeSeriesSlice, 0)
|
||||
|
||||
if context.IsDebug {
|
||||
data := simplejson.New()
|
||||
@@ -139,20 +140,20 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
Model *simplejson.Json `json:"model"`
|
||||
Datasource *simplejson.Json `json:"datasource"`
|
||||
MaxDataPoints int64 `json:"maxDataPoints"`
|
||||
IntervalMs int64 `json:"intervalMs"`
|
||||
IntervalMS int64 `json:"intervalMs"`
|
||||
}
|
||||
|
||||
queries := []*queryDto{}
|
||||
for _, q := range req.Queries {
|
||||
queries = append(queries, &queryDto{
|
||||
RefID: q.RefId,
|
||||
RefID: q.RefID,
|
||||
Model: q.Model,
|
||||
Datasource: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": q.DataSource.Id,
|
||||
"name": q.DataSource.Name,
|
||||
}),
|
||||
MaxDataPoints: q.MaxDataPoints,
|
||||
IntervalMs: q.IntervalMs,
|
||||
IntervalMS: q.IntervalMS,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -164,29 +165,30 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := c.HandleRequest(context.Ctx, getDsInfo.Result, req)
|
||||
resp, err := requestHandler.HandleRequest(context.Ctx, getDsInfo.Result, req)
|
||||
if err != nil {
|
||||
return nil, toCustomError(err)
|
||||
}
|
||||
|
||||
for _, v := range resp.Results {
|
||||
if v.Error != nil {
|
||||
return nil, fmt.Errorf("tsdb.HandleRequest() response error %v", v)
|
||||
return nil, fmt.Errorf("request handler response error %v", v)
|
||||
}
|
||||
|
||||
// If there are dataframes but no series on the result
|
||||
useDataframes := v.Dataframes != nil && (v.Series == nil || len(v.Series) == 0)
|
||||
|
||||
if useDataframes { // convert the dataframes to tsdb.TimeSeries
|
||||
if useDataframes { // convert the dataframes to plugins.DataTimeSeries
|
||||
frames, err := v.Dataframes.Decoded()
|
||||
if err != nil {
|
||||
return nil, errutil.Wrap("tsdb.HandleRequest() failed to unmarshal arrow dataframes from bytes", err)
|
||||
return nil, errutil.Wrap("request handler failed to unmarshal arrow dataframes from bytes", err)
|
||||
}
|
||||
|
||||
for _, frame := range frames {
|
||||
ss, err := FrameToSeriesSlice(frame)
|
||||
if err != nil {
|
||||
return nil, errutil.Wrapf(err, `tsdb.HandleRequest() failed to convert dataframe "%v" to tsdb.TimeSeriesSlice`, frame.Name)
|
||||
return nil, errutil.Wrapf(err,
|
||||
`request handler failed to convert dataframe "%v" to plugins.DataTimeSeriesSlice`, frame.Name)
|
||||
}
|
||||
result = append(result, ss...)
|
||||
}
|
||||
@@ -218,13 +220,14 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, timeRange *tsdb.TimeRange, debug bool) *tsdb.TsdbQuery {
|
||||
func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, timeRange plugins.DataTimeRange,
|
||||
debug bool) plugins.DataQuery {
|
||||
queryModel := c.Query.Model
|
||||
req := &tsdb.TsdbQuery{
|
||||
TimeRange: timeRange,
|
||||
Queries: []*tsdb.Query{
|
||||
req := plugins.DataQuery{
|
||||
TimeRange: &timeRange,
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
RefId: "A",
|
||||
RefID: "A",
|
||||
Model: queryModel,
|
||||
DataSource: datasource,
|
||||
QueryType: queryModel.Get("queryType").MustString(""),
|
||||
@@ -242,7 +245,6 @@ func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, t
|
||||
func newQueryCondition(model *simplejson.Json, index int) (*QueryCondition, error) {
|
||||
condition := QueryCondition{}
|
||||
condition.Index = index
|
||||
condition.HandleRequest = tsdb.HandleRequest
|
||||
|
||||
queryJSON := model.Get("query")
|
||||
|
||||
@@ -301,23 +303,23 @@ func validateToValue(to string) error {
|
||||
}
|
||||
|
||||
// FrameToSeriesSlice converts a frame that is a valid time series as per data.TimeSeriesSchema()
|
||||
// to a TimeSeriesSlice.
|
||||
func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
|
||||
// to a DataTimeSeriesSlice.
|
||||
func FrameToSeriesSlice(frame *data.Frame) (plugins.DataTimeSeriesSlice, error) {
|
||||
tsSchema := frame.TimeSeriesSchema()
|
||||
if tsSchema.Type == data.TimeSeriesTypeNot {
|
||||
// If no fields, or only a time field, create an empty tsdb.TimeSeriesSlice with a single
|
||||
// If no fields, or only a time field, create an empty plugins.DataTimeSeriesSlice with a single
|
||||
// time series in order to trigger "no data" in alerting.
|
||||
if len(frame.Fields) == 0 || (len(frame.Fields) == 1 && frame.Fields[0].Type().Time()) {
|
||||
return tsdb.TimeSeriesSlice{{
|
||||
return plugins.DataTimeSeriesSlice{{
|
||||
Name: frame.Name,
|
||||
Points: make(tsdb.TimeSeriesPoints, 0),
|
||||
Points: make(plugins.DataTimeSeriesPoints, 0),
|
||||
}}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("input frame is not recognized as a time series")
|
||||
}
|
||||
|
||||
seriesCount := len(tsSchema.ValueIndices)
|
||||
seriesSlice := make(tsdb.TimeSeriesSlice, 0, seriesCount)
|
||||
seriesSlice := make(plugins.DataTimeSeriesSlice, 0, seriesCount)
|
||||
timeField := frame.Fields[tsSchema.TimeIndex]
|
||||
timeNullFloatSlice := make([]null.Float, timeField.Len())
|
||||
|
||||
@@ -331,8 +333,8 @@ func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
|
||||
|
||||
for _, fieldIdx := range tsSchema.ValueIndices { // create a TimeSeries for each value Field
|
||||
field := frame.Fields[fieldIdx]
|
||||
ts := &tsdb.TimeSeries{
|
||||
Points: make(tsdb.TimeSeriesPoints, field.Len()),
|
||||
ts := plugins.DataTimeSeries{
|
||||
Points: make(plugins.DataTimeSeriesPoints, field.Len()),
|
||||
}
|
||||
|
||||
if len(field.Labels) > 0 {
|
||||
@@ -355,9 +357,10 @@ func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
|
||||
for rowIdx := 0; rowIdx < field.Len(); rowIdx++ { // for each value in the field, make a TimePoint
|
||||
val, err := field.FloatAt(rowIdx)
|
||||
if err != nil {
|
||||
return nil, errutil.Wrapf(err, "failed to convert frame to tsdb.series, can not convert value %v to float", field.At(rowIdx))
|
||||
return nil, errutil.Wrapf(err,
|
||||
"failed to convert frame to DataTimeSeriesSlice, can not convert value %v to float", field.At(rowIdx))
|
||||
}
|
||||
ts.Points[rowIdx] = tsdb.TimePoint{
|
||||
ts.Points[rowIdx] = plugins.DataTimePoint{
|
||||
null.FloatFrom(val),
|
||||
timeNullFloatSlice[rowIdx],
|
||||
}
|
||||
@@ -381,5 +384,5 @@ func toCustomError(err error) error {
|
||||
}
|
||||
|
||||
// generic fallback
|
||||
return fmt.Errorf("tsdb.HandleRequest() error %v", err)
|
||||
return fmt.Errorf("request handler error: %w", err)
|
||||
}
|
||||
|
||||
@@ -15,18 +15,18 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/xorcare/pointer"
|
||||
)
|
||||
|
||||
func newTimeSeriesPointsFromArgs(values ...float64) tsdb.TimeSeriesPoints {
|
||||
points := make(tsdb.TimeSeriesPoints, 0)
|
||||
func newTimeSeriesPointsFromArgs(values ...float64) plugins.DataTimeSeriesPoints {
|
||||
points := make(plugins.DataTimeSeriesPoints, 0)
|
||||
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
points = append(points, tsdb.NewTimePoint(null.FloatFrom(values[i]), values[i+1]))
|
||||
points = append(points, plugins.DataTimePoint{null.FloatFrom(values[i]), null.FloatFrom(values[i+1])})
|
||||
}
|
||||
|
||||
return points
|
||||
@@ -60,7 +60,7 @@ func TestQueryCondition(t *testing.T) {
|
||||
|
||||
Convey("should fire when avg is above 100", func() {
|
||||
points := newTimeSeriesPointsFromArgs(120, 0)
|
||||
ctx.series = tsdb.TimeSeriesSlice{&tsdb.TimeSeries{Name: "test1", Points: points}}
|
||||
ctx.series = plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "test1", Points: points}}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
@@ -80,7 +80,7 @@ func TestQueryCondition(t *testing.T) {
|
||||
|
||||
Convey("Should not fire when avg is below 100", func() {
|
||||
points := newTimeSeriesPointsFromArgs(90, 0)
|
||||
ctx.series = tsdb.TimeSeriesSlice{&tsdb.TimeSeries{Name: "test1", Points: points}}
|
||||
ctx.series = plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "test1", Points: points}}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
@@ -99,9 +99,9 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should fire if only first series matches", func() {
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs(120, 0)},
|
||||
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(0, 0)},
|
||||
ctx.series = plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs(120, 0)},
|
||||
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(0, 0)},
|
||||
}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
@@ -111,7 +111,7 @@ func TestQueryCondition(t *testing.T) {
|
||||
|
||||
Convey("No series", func() {
|
||||
Convey("Should set NoDataFound when condition is gt", func() {
|
||||
ctx.series = tsdb.TimeSeriesSlice{}
|
||||
ctx.series = plugins.DataTimeSeriesSlice{}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
@@ -121,7 +121,7 @@ func TestQueryCondition(t *testing.T) {
|
||||
|
||||
Convey("Should be firing when condition is no_value", func() {
|
||||
ctx.evaluator = `{"type": "no_value", "params": []}`
|
||||
ctx.series = tsdb.TimeSeriesSlice{}
|
||||
ctx.series = plugins.DataTimeSeriesSlice{}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
@@ -132,8 +132,8 @@ func TestQueryCondition(t *testing.T) {
|
||||
Convey("Empty series", func() {
|
||||
Convey("Should set Firing if eval match", func() {
|
||||
ctx.evaluator = `{"type": "no_value", "params": []}`
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
|
||||
ctx.series = plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
|
||||
}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
@@ -142,9 +142,9 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should set NoDataFound both series are empty", func() {
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
|
||||
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs()},
|
||||
ctx.series = plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
|
||||
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs()},
|
||||
}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
@@ -153,9 +153,9 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should set NoDataFound both series contains null", func() {
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{Name: "test1", Points: tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
|
||||
&tsdb.TimeSeries{Name: "test2", Points: tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
|
||||
ctx.series = plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{Name: "test1", Points: plugins.DataTimeSeriesPoints{plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
|
||||
plugins.DataTimeSeries{Name: "test2", Points: plugins.DataTimeSeriesPoints{plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
|
||||
}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
@@ -164,9 +164,9 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should not set NoDataFound if one series is empty", func() {
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
|
||||
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(120, 0)},
|
||||
ctx.series = plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
|
||||
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(120, 0)},
|
||||
}
|
||||
cr, err := ctx.exec()
|
||||
|
||||
@@ -181,7 +181,7 @@ func TestQueryCondition(t *testing.T) {
|
||||
type queryConditionTestContext struct {
|
||||
reducer string
|
||||
evaluator string
|
||||
series tsdb.TimeSeriesSlice
|
||||
series plugins.DataTimeSeriesSlice
|
||||
frame *data.Frame
|
||||
result *alerting.EvalContext
|
||||
condition *QueryCondition
|
||||
@@ -207,25 +207,33 @@ func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error)
|
||||
|
||||
ctx.condition = condition
|
||||
|
||||
qr := &tsdb.QueryResult{
|
||||
qr := plugins.DataQueryResult{
|
||||
Series: ctx.series,
|
||||
}
|
||||
|
||||
if ctx.frame != nil {
|
||||
qr = &tsdb.QueryResult{
|
||||
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{ctx.frame}),
|
||||
qr = plugins.DataQueryResult{
|
||||
Dataframes: plugins.NewDecodedDataFrames(data.Frames{ctx.frame}),
|
||||
}
|
||||
}
|
||||
|
||||
condition.HandleRequest = func(context context.Context, dsInfo *models.DataSource, req *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
return &tsdb.Response{
|
||||
Results: map[string]*tsdb.QueryResult{
|
||||
reqHandler := fakeReqHandler{
|
||||
response: plugins.DataResponse{
|
||||
Results: map[string]plugins.DataQueryResult{
|
||||
"A": qr,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
return condition.Eval(ctx.result)
|
||||
return condition.Eval(ctx.result, reqHandler)
|
||||
}
|
||||
|
||||
type fakeReqHandler struct {
|
||||
response plugins.DataResponse
|
||||
}
|
||||
|
||||
func (rh fakeReqHandler) HandleRequest(context.Context, *models.DataSource, plugins.DataQuery) (
|
||||
plugins.DataResponse, error) {
|
||||
return rh.response, nil
|
||||
}
|
||||
|
||||
func queryConditionScenario(desc string, fn queryConditionScenarioFunc) {
|
||||
@@ -249,7 +257,7 @@ func TestFrameToSeriesSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
frame *data.Frame
|
||||
seriesSlice tsdb.TimeSeriesSlice
|
||||
seriesSlice plugins.DataTimeSeriesSlice
|
||||
Err require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
@@ -268,21 +276,21 @@ func TestFrameToSeriesSlice(t *testing.T) {
|
||||
4.0,
|
||||
})),
|
||||
|
||||
seriesSlice: tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{
|
||||
seriesSlice: plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{
|
||||
Name: "Values Int64s {Animal Factor=cat}",
|
||||
Tags: map[string]string{"Animal Factor": "cat"},
|
||||
Points: tsdb.TimeSeriesPoints{
|
||||
tsdb.TimePoint{null.FloatFrom(math.NaN()), null.FloatFrom(1577934240000)},
|
||||
tsdb.TimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
|
||||
Points: plugins.DataTimeSeriesPoints{
|
||||
plugins.DataTimePoint{null.FloatFrom(math.NaN()), null.FloatFrom(1577934240000)},
|
||||
plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
|
||||
},
|
||||
},
|
||||
&tsdb.TimeSeries{
|
||||
plugins.DataTimeSeries{
|
||||
Name: "Values Floats {Animal Factor=sloth}",
|
||||
Tags: map[string]string{"Animal Factor": "sloth"},
|
||||
Points: tsdb.TimeSeriesPoints{
|
||||
tsdb.TimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
|
||||
tsdb.TimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
|
||||
Points: plugins.DataTimeSeriesPoints{
|
||||
plugins.DataTimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
|
||||
plugins.DataTimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -295,16 +303,16 @@ func TestFrameToSeriesSlice(t *testing.T) {
|
||||
data.NewField(`Values Int64s`, data.Labels{"Animal Factor": "cat"}, []*int64{}),
|
||||
data.NewField(`Values Floats`, data.Labels{"Animal Factor": "sloth"}, []float64{})),
|
||||
|
||||
seriesSlice: tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{
|
||||
seriesSlice: plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{
|
||||
Name: "Values Int64s {Animal Factor=cat}",
|
||||
Tags: map[string]string{"Animal Factor": "cat"},
|
||||
Points: tsdb.TimeSeriesPoints{},
|
||||
Points: plugins.DataTimeSeriesPoints{},
|
||||
},
|
||||
&tsdb.TimeSeries{
|
||||
plugins.DataTimeSeries{
|
||||
Name: "Values Floats {Animal Factor=sloth}",
|
||||
Tags: map[string]string{"Animal Factor": "sloth"},
|
||||
Points: tsdb.TimeSeriesPoints{},
|
||||
Points: plugins.DataTimeSeriesPoints{},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@@ -315,10 +323,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
|
||||
data.NewField("Time", data.Labels{}, []time.Time{}),
|
||||
data.NewField(`Values`, data.Labels{}, []float64{})),
|
||||
|
||||
seriesSlice: tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{
|
||||
seriesSlice: plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{
|
||||
Name: "Values",
|
||||
Points: tsdb.TimeSeriesPoints{},
|
||||
Points: plugins.DataTimeSeriesPoints{},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@@ -331,10 +339,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
|
||||
DisplayNameFromDS: "sloth",
|
||||
})),
|
||||
|
||||
seriesSlice: tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{
|
||||
seriesSlice: plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{
|
||||
Name: "sloth",
|
||||
Points: tsdb.TimeSeriesPoints{},
|
||||
Points: plugins.DataTimeSeriesPoints{},
|
||||
Tags: map[string]string{"Rating": "10"},
|
||||
},
|
||||
},
|
||||
@@ -349,10 +357,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
|
||||
DisplayNameFromDS: "sloth #2",
|
||||
})),
|
||||
|
||||
seriesSlice: tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{
|
||||
seriesSlice: plugins.DataTimeSeriesSlice{
|
||||
plugins.DataTimeSeries{
|
||||
Name: "sloth #1",
|
||||
Points: tsdb.TimeSeriesPoints{},
|
||||
Points: plugins.DataTimeSeriesPoints{},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
// queryReducer reduces a timeseries to a nullable float
|
||||
@@ -18,7 +18,7 @@ type queryReducer struct {
|
||||
}
|
||||
|
||||
//nolint: gocyclo
|
||||
func (s *queryReducer) Reduce(series *tsdb.TimeSeries) null.Float {
|
||||
func (s *queryReducer) Reduce(series plugins.DataTimeSeries) null.Float {
|
||||
if len(series.Points) == 0 {
|
||||
return null.FloatFromPtr(nil)
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func newSimpleReducer(t string) *queryReducer {
|
||||
return &queryReducer{Type: t}
|
||||
}
|
||||
|
||||
func calculateDiff(series *tsdb.TimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
|
||||
func calculateDiff(series plugins.DataTimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
|
||||
var (
|
||||
points = series.Points
|
||||
first float64
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
func TestSimpleReducer(t *testing.T) {
|
||||
@@ -54,16 +54,16 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
Convey("median should ignore null values", func() {
|
||||
reducer := newSimpleReducer("median")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 3))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(1)), 4))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(2)), 5))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(3)), 6))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(3)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(1)), null.FloatFrom(4)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(2)), null.FloatFrom(5)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(3)), null.FloatFrom(6)})
|
||||
|
||||
result := reducer.Reduce(series)
|
||||
So(result.Valid, ShouldEqual, true)
|
||||
@@ -77,25 +77,25 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
Convey("avg with only nulls", func() {
|
||||
reducer := newSimpleReducer("avg")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
|
||||
So(reducer.Reduce(series).Valid, ShouldEqual, false)
|
||||
})
|
||||
|
||||
Convey("count_non_null", func() {
|
||||
Convey("with null values and real values", func() {
|
||||
reducer := newSimpleReducer("count_non_null")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 3))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 4))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(3)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(4)})
|
||||
|
||||
So(reducer.Reduce(series).Valid, ShouldEqual, true)
|
||||
So(reducer.Reduce(series).Float64, ShouldEqual, 2)
|
||||
@@ -103,12 +103,12 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
Convey("with null values", func() {
|
||||
reducer := newSimpleReducer("count_non_null")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
|
||||
|
||||
So(reducer.Reduce(series).Valid, ShouldEqual, false)
|
||||
})
|
||||
@@ -116,14 +116,14 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
Convey("avg of number values and null values should ignore nulls", func() {
|
||||
reducer := newSimpleReducer("avg")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 1))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 3))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 4))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(1)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(3)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(4)})
|
||||
|
||||
So(reducer.Reduce(series).Float64, ShouldEqual, float64(3))
|
||||
})
|
||||
@@ -181,12 +181,12 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
Convey("diff with only nulls", func() {
|
||||
reducer := newSimpleReducer("diff")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
|
||||
|
||||
So(reducer.Reduce(series).Valid, ShouldEqual, false)
|
||||
})
|
||||
@@ -244,12 +244,12 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
Convey("diff_abs with only nulls", func() {
|
||||
reducer := newSimpleReducer("diff_abs")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
|
||||
|
||||
So(reducer.Reduce(series).Valid, ShouldEqual, false)
|
||||
})
|
||||
@@ -307,12 +307,12 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
Convey("percent_diff with only nulls", func() {
|
||||
reducer := newSimpleReducer("percent_diff")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
|
||||
|
||||
So(reducer.Reduce(series).Valid, ShouldEqual, false)
|
||||
})
|
||||
@@ -370,12 +370,12 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
Convey("percent_diff_abs with only nulls", func() {
|
||||
reducer := newSimpleReducer("percent_diff_abs")
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
|
||||
|
||||
So(reducer.Reduce(series).Valid, ShouldEqual, false)
|
||||
})
|
||||
@@ -399,12 +399,12 @@ func TestSimpleReducer(t *testing.T) {
|
||||
|
||||
func testReducer(reducerType string, datapoints ...float64) float64 {
|
||||
reducer := newSimpleReducer(reducerType)
|
||||
series := &tsdb.TimeSeries{
|
||||
series := plugins.DataTimeSeries{
|
||||
Name: "test time series",
|
||||
}
|
||||
|
||||
for idx := range datapoints {
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(datapoints[idx]), 1234134))
|
||||
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(datapoints[idx]), null.FloatFrom(1234134)})
|
||||
}
|
||||
|
||||
return reducer.Reduce(series).Float64
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
tlog "github.com/opentracing/opentracing-go/log"
|
||||
@@ -26,6 +27,7 @@ type AlertEngine struct {
|
||||
RenderService rendering.Service `inject:""`
|
||||
Bus bus.Bus `inject:""`
|
||||
RequestValidator models.PluginRequestValidator `inject:""`
|
||||
DataService *tsdb.Service `inject:""`
|
||||
|
||||
execQueue chan *Job
|
||||
ticker *Ticker
|
||||
@@ -50,7 +52,7 @@ func (e *AlertEngine) Init() error {
|
||||
e.ticker = NewTicker(time.Now(), time.Second*0, clock.New(), 1)
|
||||
e.execQueue = make(chan *Job, 1000)
|
||||
e.scheduler = newScheduler()
|
||||
e.evalHandler = NewEvalHandler()
|
||||
e.evalHandler = NewEvalHandler(e.DataService)
|
||||
e.ruleReader = newRuleReader()
|
||||
e.log = log.New("alerting.engine")
|
||||
e.resultHandler = newResultHandler(e.RenderService)
|
||||
|
||||
@@ -7,19 +7,22 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
|
||||
)
|
||||
|
||||
// DefaultEvalHandler is responsible for evaluating the alert rule.
|
||||
type DefaultEvalHandler struct {
|
||||
log log.Logger
|
||||
alertJobTimeout time.Duration
|
||||
requestHandler tsdbifaces.RequestHandler
|
||||
}
|
||||
|
||||
// NewEvalHandler is the `DefaultEvalHandler` constructor.
|
||||
func NewEvalHandler() *DefaultEvalHandler {
|
||||
func NewEvalHandler(requestHandler tsdbifaces.RequestHandler) *DefaultEvalHandler {
|
||||
return &DefaultEvalHandler{
|
||||
log: log.New("alerting.evalHandler"),
|
||||
alertJobTimeout: time.Second * 5,
|
||||
requestHandler: requestHandler,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +34,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
|
||||
|
||||
for i := 0; i < len(context.Rule.Conditions); i++ {
|
||||
condition := context.Rule.Conditions[i]
|
||||
cr, err := condition.Eval(context)
|
||||
cr, err := condition.Eval(context, e.requestHandler)
|
||||
if err != nil {
|
||||
context.Error = err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
@@ -16,13 +17,13 @@ type conditionStub struct {
|
||||
noData bool
|
||||
}
|
||||
|
||||
func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) {
|
||||
func (c *conditionStub) Eval(context *EvalContext, reqHandler tsdbifaces.RequestHandler) (*ConditionResult, error) {
|
||||
return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator, NoDataFound: c.noData}, nil
|
||||
}
|
||||
|
||||
func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
Convey("Test alert evaluation handler", t, func() {
|
||||
handler := NewEvalHandler()
|
||||
handler := NewEvalHandler(nil)
|
||||
|
||||
Convey("Show return triggered with single passing condition", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
|
||||
)
|
||||
|
||||
type evalHandler interface {
|
||||
@@ -59,5 +60,5 @@ type ConditionResult struct {
|
||||
|
||||
// Condition is responsible for evaluating an alert condition.
|
||||
type Condition interface {
|
||||
Eval(result *EvalContext) (*ConditionResult, error)
|
||||
Eval(result *EvalContext, requestHandler tsdbifaces.RequestHandler) (*ConditionResult, error)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
|
||||
type FakeCondition struct{}
|
||||
|
||||
func (f *FakeCondition) Eval(context *EvalContext) (*ConditionResult, error) {
|
||||
func (f *FakeCondition) Eval(context *EvalContext, reqHandler tsdbifaces.RequestHandler) (*ConditionResult, error) {
|
||||
return &ConditionResult{}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,59 +4,39 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// AlertTestCommand initiates an test evaluation
|
||||
// of an alert rule.
|
||||
type AlertTestCommand struct {
|
||||
Dashboard *simplejson.Json
|
||||
PanelID int64
|
||||
OrgID int64
|
||||
User *models.SignedInUser
|
||||
// AlertTest makes a test alert.
|
||||
func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID int64, user *models.SignedInUser) (*EvalContext, error) {
|
||||
dash := models.NewDashboardFromJson(dashboard)
|
||||
|
||||
Result *EvalContext
|
||||
}
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("alerting", handleAlertTestCommand)
|
||||
}
|
||||
|
||||
func handleAlertTestCommand(cmd *AlertTestCommand) error {
|
||||
dash := models.NewDashboardFromJson(cmd.Dashboard)
|
||||
|
||||
extractor := NewDashAlertExtractor(dash, cmd.OrgID, cmd.User)
|
||||
extractor := NewDashAlertExtractor(dash, orgID, user)
|
||||
alerts, err := extractor.GetAlerts()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
if alert.PanelId == cmd.PanelID {
|
||||
rule, err := NewRuleFromDBAlert(alert, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = testAlertRule(rule)
|
||||
return nil
|
||||
if alert.PanelId != panelID {
|
||||
continue
|
||||
}
|
||||
rule, err := NewRuleFromDBAlert(alert, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler := NewEvalHandler(e.DataService)
|
||||
|
||||
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{})
|
||||
context.IsTestRun = true
|
||||
context.IsDebug = true
|
||||
|
||||
handler.Eval(context)
|
||||
context.Rule.State = context.GetNewState()
|
||||
return context, nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("could not find alert with panel ID %d", cmd.PanelID)
|
||||
}
|
||||
|
||||
func testAlertRule(rule *Rule) *EvalContext {
|
||||
handler := NewEvalHandler()
|
||||
|
||||
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{})
|
||||
context.IsTestRun = true
|
||||
context.IsDebug = true
|
||||
|
||||
handler.Eval(context)
|
||||
context.Rule.State = context.GetNewState()
|
||||
|
||||
return context
|
||||
return nil, fmt.Errorf("could not find alert with panel ID %d", panelID)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/gtime"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -33,9 +34,10 @@ type DashboardProvisioningService interface {
|
||||
}
|
||||
|
||||
// NewService factory for creating a new dashboard service
|
||||
var NewService = func() DashboardService {
|
||||
var NewService = func(reqHandler tsdbifaces.RequestHandler) DashboardService {
|
||||
return &dashboardServiceImpl{
|
||||
log: log.New("dashboard-service"),
|
||||
log: log.New("dashboard-service"),
|
||||
reqHandler: reqHandler,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +58,10 @@ type SaveDashboardDTO struct {
|
||||
}
|
||||
|
||||
type dashboardServiceImpl struct {
|
||||
orgId int64
|
||||
user *models.SignedInUser
|
||||
log log.Logger
|
||||
orgId int64
|
||||
user *models.SignedInUser
|
||||
log log.Logger
|
||||
reqHandler tsdbifaces.RequestHandler
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||
@@ -386,7 +389,7 @@ func (s *FakeDashboardService) DeleteDashboard(dashboardId int64, orgId int64) e
|
||||
}
|
||||
|
||||
func MockDashboardService(mock *FakeDashboardService) {
|
||||
NewService = func() DashboardService {
|
||||
NewService = func(tsdbifaces.RequestHandler) DashboardService {
|
||||
return mock
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,7 +763,7 @@ func createDashboard(t *testing.T, user models.SignedInUser, title string, folde
|
||||
return nil
|
||||
})
|
||||
|
||||
dashboard, err := dashboards.NewService().SaveDashboard(dashItem, true)
|
||||
dashboard, err := dashboards.NewService(nil).SaveDashboard(dashItem, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
return dashboard
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/live/features"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -222,7 +222,7 @@ func (g *GrafanaLive) GetChannelHandlerFactory(scope string, name string) (model
|
||||
}, nil
|
||||
}
|
||||
|
||||
p, ok := plugins.Plugins[name]
|
||||
p, ok := manager.Plugins[name]
|
||||
if ok {
|
||||
h := &PluginHandler{
|
||||
Plugin: p,
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -20,6 +21,7 @@ type apiImpl struct {
|
||||
Cfg *setting.Cfg `inject:""`
|
||||
DatasourceCache datasources.CacheService `inject:""`
|
||||
RouteRegister routing.RouteRegister `inject:""`
|
||||
DataService *tsdb.Service
|
||||
schedule scheduleService
|
||||
store store
|
||||
}
|
||||
@@ -64,13 +66,13 @@ func (api *apiImpl) conditionEvalEndpoint(c *models.ReqContext, cmd evalAlertCon
|
||||
}
|
||||
|
||||
evaluator := eval.Evaluator{Cfg: api.Cfg}
|
||||
evalResults, err := evaluator.ConditionEval(&evalCond, timeNow())
|
||||
evalResults, err := evaluator.ConditionEval(&evalCond, timeNow(), api.DataService)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to evaluate conditions", err)
|
||||
}
|
||||
|
||||
frame := evalResults.AsDataFrame()
|
||||
df := tsdb.NewDecodedDataFrames([]*data.Frame{&frame})
|
||||
df := plugins.NewDecodedDataFrames([]*data.Frame{&frame})
|
||||
instances, err := df.Encoded()
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to encode result dataframes", err)
|
||||
@@ -95,13 +97,13 @@ func (api *apiImpl) alertDefinitionEvalEndpoint(c *models.ReqContext) response.R
|
||||
}
|
||||
|
||||
evaluator := eval.Evaluator{Cfg: api.Cfg}
|
||||
evalResults, err := evaluator.ConditionEval(condition, timeNow())
|
||||
evalResults, err := evaluator.ConditionEval(condition, timeNow(), api.DataService)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to evaluate alert", err)
|
||||
}
|
||||
frame := evalResults.AsDataFrame()
|
||||
|
||||
df := tsdb.NewDecodedDataFrames([]*data.Frame{&frame})
|
||||
df := plugins.NewDecodedDataFrames([]*data.Frame{&frame})
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to instantiate Dataframes from the decoded frames", err)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
@@ -100,7 +101,7 @@ type AlertExecCtx struct {
|
||||
}
|
||||
|
||||
// execute runs the Condition's expressions or queries.
|
||||
func (c *Condition) execute(ctx AlertExecCtx, now time.Time) (*ExecutionResults, error) {
|
||||
func (c *Condition) execute(ctx AlertExecCtx, now time.Time, dataService *tsdb.Service) (*ExecutionResults, error) {
|
||||
result := ExecutionResults{}
|
||||
if !c.IsValid() {
|
||||
return nil, fmt.Errorf("invalid conditions")
|
||||
@@ -140,7 +141,10 @@ func (c *Condition) execute(ctx AlertExecCtx, now time.Time) (*ExecutionResults,
|
||||
})
|
||||
}
|
||||
|
||||
exprService := expr.Service{Cfg: &setting.Cfg{ExpressionsEnabled: ctx.ExpressionsEnabled}}
|
||||
exprService := expr.Service{
|
||||
Cfg: &setting.Cfg{ExpressionsEnabled: ctx.ExpressionsEnabled},
|
||||
DataService: dataService,
|
||||
}
|
||||
pbRes, err := exprService.TransformData(ctx.Ctx, queryDataReq)
|
||||
if err != nil {
|
||||
return &result, err
|
||||
@@ -218,13 +222,13 @@ func (evalResults Results) AsDataFrame() data.Frame {
|
||||
}
|
||||
|
||||
// ConditionEval executes conditions and evaluates the result.
|
||||
func (e *Evaluator) ConditionEval(condition *Condition, now time.Time) (Results, error) {
|
||||
func (e *Evaluator) ConditionEval(condition *Condition, now time.Time, dataService *tsdb.Service) (Results, error) {
|
||||
alertCtx, cancelFn := context.WithTimeout(context.Background(), alertingEvaluationTimeout)
|
||||
defer cancelFn()
|
||||
|
||||
alertExecCtx := AlertExecCtx{OrgID: condition.OrgID, Ctx: alertCtx, ExpressionsEnabled: e.Cfg.ExpressionsEnabled}
|
||||
|
||||
execResult, err := condition.execute(alertExecCtx, now)
|
||||
execResult, err := condition.execute(alertExecCtx, now, dataService)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute conditions: %w", err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -34,6 +35,7 @@ type AlertNG struct {
|
||||
DatasourceCache datasources.CacheService `inject:""`
|
||||
RouteRegister routing.RouteRegister `inject:""`
|
||||
SQLStore *sqlstore.SQLStore `inject:""`
|
||||
DataService *tsdb.Service `inject:""`
|
||||
log log.Logger
|
||||
schedule scheduleService
|
||||
}
|
||||
@@ -57,14 +59,16 @@ func (ng *AlertNG) Init() error {
|
||||
evaluator: eval.Evaluator{Cfg: ng.Cfg},
|
||||
store: store,
|
||||
}
|
||||
ng.schedule = newScheduler(schedCfg)
|
||||
ng.schedule = newScheduler(schedCfg, ng.DataService)
|
||||
|
||||
api := apiImpl{
|
||||
Cfg: ng.Cfg,
|
||||
DatasourceCache: ng.DatasourceCache,
|
||||
RouteRegister: ng.RouteRegister,
|
||||
DataService: ng.DataService,
|
||||
schedule: ng.schedule,
|
||||
store: store}
|
||||
store: store,
|
||||
}
|
||||
api.registerAPIEndpoints()
|
||||
|
||||
return nil
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -24,7 +25,8 @@ type scheduleService interface {
|
||||
overrideCfg(cfg schedulerCfg)
|
||||
}
|
||||
|
||||
func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefinitionKey, evalCh <-chan *evalContext, stopCh <-chan struct{}) error {
|
||||
func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefinitionKey,
|
||||
evalCh <-chan *evalContext, stopCh <-chan struct{}) error {
|
||||
sch.log.Debug("alert definition routine started", "key", key)
|
||||
|
||||
evalRunning := false
|
||||
@@ -58,11 +60,12 @@ func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefi
|
||||
OrgID: alertDefinition.OrgID,
|
||||
QueriesAndExpressions: alertDefinition.Data,
|
||||
}
|
||||
results, err := sch.evaluator.ConditionEval(&condition, ctx.now)
|
||||
results, err := sch.evaluator.ConditionEval(&condition, ctx.now, sch.dataService)
|
||||
end = timeNow()
|
||||
if err != nil {
|
||||
// consider saving alert instance on error
|
||||
sch.log.Error("failed to evaluate alert definition", "title", alertDefinition.Title, "key", key, "attempt", attempt, "now", ctx.now, "duration", end.Sub(start), "error", err)
|
||||
sch.log.Error("failed to evaluate alert definition", "title", alertDefinition.Title,
|
||||
"key", key, "attempt", attempt, "now", ctx.now, "duration", end.Sub(start), "error", err)
|
||||
return err
|
||||
}
|
||||
for _, r := range results {
|
||||
@@ -129,6 +132,8 @@ type schedule struct {
|
||||
evaluator eval.Evaluator
|
||||
|
||||
store store
|
||||
|
||||
dataService *tsdb.Service
|
||||
}
|
||||
|
||||
type schedulerCfg struct {
|
||||
@@ -142,7 +147,7 @@ type schedulerCfg struct {
|
||||
}
|
||||
|
||||
// newScheduler returns a new schedule.
|
||||
func newScheduler(cfg schedulerCfg) *schedule {
|
||||
func newScheduler(cfg schedulerCfg, dataService *tsdb.Service) *schedule {
|
||||
ticker := alerting.NewTicker(cfg.c.Now(), time.Second*0, cfg.c, int64(cfg.baseInterval.Seconds()))
|
||||
sch := schedule{
|
||||
registry: alertDefinitionRegistry{alertDefinitionInfo: make(map[alertDefinitionKey]alertDefinitionInfo)},
|
||||
@@ -155,6 +160,7 @@ func newScheduler(cfg schedulerCfg) *schedule {
|
||||
stopAppliedFunc: cfg.stopAppliedFunc,
|
||||
evaluator: cfg.evaluator,
|
||||
store: cfg.store,
|
||||
dataService: dataService,
|
||||
}
|
||||
return &sch
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@@ -112,7 +112,7 @@ func validatePluginsConfig(apps []*pluginsAsConfig) error {
|
||||
}
|
||||
|
||||
for _, app := range apps[i].Apps {
|
||||
if !plugins.IsAppInstalled(app.PluginID) {
|
||||
if !manager.IsAppInstalled(app.PluginID) {
|
||||
return fmt.Errorf("app plugin not installed: %s", app.PluginID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
const (
|
||||
incorrectSettings = "./testdata/test-configs/incorrect-settings"
|
||||
brokenYaml = "./testdata/test-configs/broken-yaml"
|
||||
emptyFolder = "./testdata/test-configs/empty_folder"
|
||||
@@ -46,7 +47,7 @@ func TestConfigReader(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Can read correct properties", func(t *testing.T) {
|
||||
plugins.Apps = map[string]*plugins.AppPlugin{
|
||||
manager.Apps = map[string]*plugins.AppPlugin{
|
||||
"test-plugin": {},
|
||||
"test-plugin-2": {},
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -86,7 +87,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
|
||||
|
||||
if rs.pluginAvailable() {
|
||||
rs.log = rs.log.New("renderer", "plugin")
|
||||
rs.pluginInfo = plugins.Renderer
|
||||
rs.pluginInfo = manager.Renderer
|
||||
|
||||
if err := rs.startPlugin(ctx); err != nil {
|
||||
return err
|
||||
@@ -106,7 +107,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (rs *RenderingService) pluginAvailable() bool {
|
||||
return plugins.Renderer != nil
|
||||
return manager.Renderer != nil
|
||||
}
|
||||
|
||||
func (rs *RenderingService) remoteAvailable() bool {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -34,11 +35,11 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
savedFolder := saveTestFolder("Saved folder", testOrgId)
|
||||
savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
|
||||
saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
|
||||
savedDashInGeneralFolder := saveTestDashboard("Saved dashboard in general folder", testOrgId, 0)
|
||||
otherSavedFolder := saveTestFolder("Other saved folder", testOrgId)
|
||||
savedFolder := saveTestFolder(t, "Saved folder", testOrgId)
|
||||
savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgId, savedFolder.Id)
|
||||
saveTestDashboard(t, "Other saved dash in folder", testOrgId, savedFolder.Id)
|
||||
savedDashInGeneralFolder := saveTestDashboard(t, "Saved dashboard in general folder", testOrgId, 0)
|
||||
otherSavedFolder := saveTestFolder(t, "Other saved folder", testOrgId)
|
||||
|
||||
Convey("Should return dashboard model", func() {
|
||||
So(savedFolder.Title, ShouldEqual, "Saved folder")
|
||||
@@ -110,7 +111,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: false,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
|
||||
Convey("It should create a new dashboard in organization B", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
@@ -385,7 +386,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
@@ -409,7 +410,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
@@ -434,7 +435,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new folder", func() {
|
||||
@@ -459,7 +460,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
@@ -484,7 +485,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
@@ -545,7 +546,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
@@ -590,7 +591,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
@@ -676,7 +677,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
@@ -701,7 +702,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
@@ -726,7 +727,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
@@ -772,7 +773,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
|
||||
Convey("It should overwrite existing dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
@@ -799,7 +800,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
res := callSaveWithResult(t, cmd)
|
||||
|
||||
Convey("It should overwrite existing dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
@@ -962,19 +963,25 @@ func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenari
|
||||
dashboardPermissionScenario(desc, mock, fn)
|
||||
}
|
||||
|
||||
func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
|
||||
func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand) *models.Dashboard {
|
||||
t.Helper()
|
||||
|
||||
dto := toSaveDashboardDto(cmd)
|
||||
res, _ := dashboards.NewService().SaveDashboard(&dto, false)
|
||||
res, err := dashboards.NewService(nil).SaveDashboard(&dto, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func callSaveWithError(cmd models.SaveDashboardCommand) error {
|
||||
dto := toSaveDashboardDto(cmd)
|
||||
_, err := dashboards.NewService().SaveDashboard(&dto, false)
|
||||
_, err := dashboards.NewService(nil).SaveDashboard(&dto, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
|
||||
func saveTestDashboard(t *testing.T, title string, orgId int64, folderId int64) *models.Dashboard {
|
||||
t.Helper()
|
||||
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
@@ -994,13 +1001,14 @@ func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashbo
|
||||
},
|
||||
}
|
||||
|
||||
res, err := dashboards.NewService().SaveDashboard(&dto, false)
|
||||
So(err, ShouldBeNil)
|
||||
res, err := dashboards.NewService(nil).SaveDashboard(&dto, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func saveTestFolder(title string, orgId int64) *models.Dashboard {
|
||||
func saveTestFolder(t *testing.T, title string, orgId int64) *models.Dashboard {
|
||||
t.Helper()
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: 0,
|
||||
@@ -1020,8 +1028,8 @@ func saveTestFolder(title string, orgId int64) *models.Dashboard {
|
||||
},
|
||||
}
|
||||
|
||||
res, err := dashboards.NewService().SaveDashboard(&dto, false)
|
||||
So(err, ShouldBeNil)
|
||||
res, err := dashboards.NewService(nil).SaveDashboard(&dto, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user