mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Remove unused code in legacydata package (#88772)
This commit is contained in:
parent
df784917e4
commit
0fffa31931
@ -164,8 +164,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/graphite"
|
"github.com/grafana/grafana/pkg/tsdb/graphite"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/influxdb"
|
"github.com/grafana/grafana/pkg/tsdb/influxdb"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
|
||||||
legacydataservice "github.com/grafana/grafana/pkg/tsdb/legacydata/service"
|
|
||||||
"github.com/grafana/grafana/pkg/tsdb/loki"
|
"github.com/grafana/grafana/pkg/tsdb/loki"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/mssql"
|
"github.com/grafana/grafana/pkg/tsdb/mssql"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/mysql"
|
"github.com/grafana/grafana/pkg/tsdb/mysql"
|
||||||
@ -176,8 +174,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var wireBasicSet = wire.NewSet(
|
var wireBasicSet = wire.NewSet(
|
||||||
legacydataservice.ProvideService,
|
|
||||||
wire.Bind(new(legacydata.RequestHandler), new(*legacydataservice.Service)),
|
|
||||||
annotationsimpl.ProvideService,
|
annotationsimpl.ProvideService,
|
||||||
wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)),
|
wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)),
|
||||||
New,
|
New,
|
||||||
|
@ -26,7 +26,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = log.New("tsdb.graphite")
|
var logger = log.New("tsdb.graphite")
|
||||||
@ -351,7 +350,7 @@ func epochMStoGraphiteTime(tr backend.TimeRange) (string, string) {
|
|||||||
/**
|
/**
|
||||||
* Graphite should always return timestamp as a number but values might be nil when data is missing
|
* Graphite should always return timestamp as a number but values might be nil when data is missing
|
||||||
*/
|
*/
|
||||||
func parseDataTimePoint(dataTimePoint legacydata.DataTimePoint) (time.Time, *float64, error) {
|
func parseDataTimePoint(dataTimePoint DataTimePoint) (time.Time, *float64, error) {
|
||||||
if !dataTimePoint[1].Valid {
|
if !dataTimePoint[1].Valid {
|
||||||
return time.Time{}, nil, errors.New("failed to parse data point timestamp")
|
return time.Time{}, nil, errors.New("failed to parse data point timestamp")
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package graphite
|
package graphite
|
||||||
|
|
||||||
import "github.com/grafana/grafana/pkg/tsdb/legacydata"
|
import (
|
||||||
|
"github.com/grafana/grafana/pkg/components/null"
|
||||||
|
)
|
||||||
|
|
||||||
type TargetResponseDTO struct {
|
type TargetResponseDTO struct {
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
DataPoints legacydata.DataTimeSeriesPoints `json:"datapoints"`
|
DataPoints DataTimeSeriesPoints `json:"datapoints"`
|
||||||
// Graphite <=1.1.7 may return some tags as numbers requiring extra conversion. See https://github.com/grafana/grafana/issues/37614
|
// Graphite <=1.1.7 may return some tags as numbers requiring extra conversion. See https://github.com/grafana/grafana/issues/37614
|
||||||
Tags map[string]any `json:"tags"`
|
Tags map[string]any `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DataTimePoint [2]null.Float
|
||||||
|
type DataTimeSeriesPoints []DataTimePoint
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
package legacydata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/null"
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RequestHandler is a data request handler interface.
|
|
||||||
// Deprecated: use backend.QueryDataHandler instead.
|
|
||||||
type RequestHandler interface {
|
|
||||||
// HandleRequest handles a data request.
|
|
||||||
HandleRequest(context.Context, *datasources.DataSource, DataQuery) (DataResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataSubQuery represents a data sub-query. New work should use the plugin SDK.
|
|
||||||
type DataSubQuery struct {
|
|
||||||
RefID string `json:"refId"`
|
|
||||||
Model *simplejson.Json `json:"model,omitempty"`
|
|
||||||
DataSource *datasources.DataSource `json:"datasource"`
|
|
||||||
MaxDataPoints int64 `json:"maxDataPoints"`
|
|
||||||
IntervalMS int64 `json:"intervalMs"`
|
|
||||||
QueryType string `json:"queryType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataQuery contains all information about a data query request. New work should use the plugin SDK.
|
|
||||||
type DataQuery struct {
|
|
||||||
TimeRange *DataTimeRange
|
|
||||||
Queries []DataSubQuery
|
|
||||||
Headers map[string]string
|
|
||||||
Debug bool
|
|
||||||
User *user.SignedInUser
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataTable struct {
|
|
||||||
Columns []DataTableColumn `json:"columns"`
|
|
||||||
Rows []DataRowValues `json:"rows"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataTableColumn struct {
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataTimePoint [2]null.Float
|
|
||||||
type DataTimeSeriesPoints []DataTimePoint
|
|
||||||
type DataTimeSeriesSlice []DataTimeSeries
|
|
||||||
type DataRowValues []any
|
|
||||||
|
|
||||||
// Deprecated: DataQueryResult should use backend.QueryDataResponse
|
|
||||||
type DataQueryResult struct {
|
|
||||||
Error error `json:"-"`
|
|
||||||
ErrorString string `json:"error,omitempty"`
|
|
||||||
RefID string `json:"refId"`
|
|
||||||
Meta *simplejson.Json `json:"meta,omitempty"`
|
|
||||||
Series DataTimeSeriesSlice `json:"series"`
|
|
||||||
Tables []DataTable `json:"tables"`
|
|
||||||
Dataframes DataFrames `json:"dataframes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON deserializes a DataQueryResult from JSON.
|
|
||||||
//
|
|
||||||
// Deserialization support is required by tests.
|
|
||||||
func (r *DataQueryResult) UnmarshalJSON(b []byte) error {
|
|
||||||
m := map[string]any{}
|
|
||||||
if err := json.Unmarshal(b, &m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
refID, ok := m["refId"].(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field refId - not a string")
|
|
||||||
}
|
|
||||||
var meta *simplejson.Json
|
|
||||||
if m["meta"] != nil {
|
|
||||||
mm, ok := m["meta"].(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field meta - not a JSON object")
|
|
||||||
}
|
|
||||||
meta = simplejson.NewFromAny(mm)
|
|
||||||
}
|
|
||||||
var series DataTimeSeriesSlice
|
|
||||||
/* TODO
|
|
||||||
if m["series"] != nil {
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
var tables []DataTable
|
|
||||||
if m["tables"] != nil {
|
|
||||||
ts, ok := m["tables"].([]any)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field tables - not an array of Tables")
|
|
||||||
}
|
|
||||||
for _, ti := range ts {
|
|
||||||
tm, ok := ti.(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field tables - not an array of Tables")
|
|
||||||
}
|
|
||||||
var columns []DataTableColumn
|
|
||||||
cs, ok := tm["columns"].([]any)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field tables - not an array of Tables")
|
|
||||||
}
|
|
||||||
for _, ci := range cs {
|
|
||||||
cm, ok := ci.(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field tables - not an array of Tables")
|
|
||||||
}
|
|
||||||
val, ok := cm["text"].(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field tables - not an array of Tables")
|
|
||||||
}
|
|
||||||
|
|
||||||
columns = append(columns, DataTableColumn{Text: val})
|
|
||||||
}
|
|
||||||
|
|
||||||
rs, ok := tm["rows"].([]any)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field tables - not an array of Tables")
|
|
||||||
}
|
|
||||||
var rows []DataRowValues
|
|
||||||
for _, ri := range rs {
|
|
||||||
vals, ok := ri.([]any)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field tables - not an array of Tables")
|
|
||||||
}
|
|
||||||
rows = append(rows, vals)
|
|
||||||
}
|
|
||||||
|
|
||||||
tables = append(tables, DataTable{
|
|
||||||
Columns: columns,
|
|
||||||
Rows: rows,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var dfs *dataFrames
|
|
||||||
if m["dataframes"] != nil {
|
|
||||||
raw, ok := m["dataframes"].([]any)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field dataframes - not an array of byte arrays")
|
|
||||||
}
|
|
||||||
|
|
||||||
var encoded [][]byte
|
|
||||||
for _, ra := range raw {
|
|
||||||
encS, ok := ra.(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't decode field dataframes - not an array of byte arrays")
|
|
||||||
}
|
|
||||||
enc, err := base64.StdEncoding.DecodeString(encS)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't decode field dataframes - not an array of arrow frames")
|
|
||||||
}
|
|
||||||
encoded = append(encoded, enc)
|
|
||||||
}
|
|
||||||
decoded, err := data.UnmarshalArrowFrames(encoded)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dfs = &dataFrames{
|
|
||||||
decoded: decoded,
|
|
||||||
encoded: encoded,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.RefID = refID
|
|
||||||
r.Meta = meta
|
|
||||||
r.Series = series
|
|
||||||
r.Tables = tables
|
|
||||||
if dfs != nil {
|
|
||||||
r.Dataframes = dfs
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataTimeSeries -- this structure is deprecated, all new work should use DataFrames from the SDK
|
|
||||||
type DataTimeSeries struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Points DataTimeSeriesPoints `json:"points"`
|
|
||||||
Tags map[string]string `json:"tags,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: DataResponse -- this structure is deprecated, all new work should use backend.QueryDataResponse
|
|
||||||
type DataResponse struct {
|
|
||||||
Results map[string]DataQueryResult `json:"results"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
package legacydata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DataFrames is an interface for retrieving encoded and decoded data frames.
|
|
||||||
//
|
|
||||||
// See NewDecodedDataFrames and NewEncodedDataFrames for more information.
|
|
||||||
type DataFrames interface {
|
|
||||||
// Encoded encodes Frames into a slice of []byte.
|
|
||||||
// If an error occurs [][]byte will be nil.
|
|
||||||
// The encoded result, if any, will be cached and returned next time Encoded is called.
|
|
||||||
Encoded() ([][]byte, error)
|
|
||||||
|
|
||||||
// Decoded decodes a slice of Arrow encoded frames to data.Frames ([]*data.Frame).
|
|
||||||
// If an error occurs Frames will be nil.
|
|
||||||
// The decoded result, if any, will be cached and returned next time Decoded is called.
|
|
||||||
Decoded() (data.Frames, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dataFrames struct {
|
|
||||||
decoded data.Frames
|
|
||||||
encoded [][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecodedDataFrames instantiates DataFrames from decoded frames.
|
|
||||||
//
|
|
||||||
// This should be the primary function for creating DataFrames if you're implementing a plugin.
|
|
||||||
// In a Grafana Alerting scenario it needs to operate on decoded frames, which is why this function is
|
|
||||||
// preferrable. When encoded data frames are needed, e.g. returned from Grafana HTTP API, it will
|
|
||||||
// happen automatically when MarshalJSON() is called.
|
|
||||||
func NewDecodedDataFrames(decodedFrames data.Frames) DataFrames {
|
|
||||||
return &dataFrames{
|
|
||||||
decoded: decodedFrames,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (df *dataFrames) Encoded() ([][]byte, error) {
|
|
||||||
if df.encoded == nil {
|
|
||||||
encoded, err := df.decoded.MarshalArrow()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
df.encoded = encoded
|
|
||||||
}
|
|
||||||
|
|
||||||
return df.encoded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (df *dataFrames) Decoded() (data.Frames, error) {
|
|
||||||
if df.decoded == nil {
|
|
||||||
decoded, err := data.UnmarshalArrowFrames(df.encoded)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
df.decoded = decoded
|
|
||||||
}
|
|
||||||
|
|
||||||
return df.decoded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (df *dataFrames) MarshalJSON() ([]byte, error) {
|
|
||||||
encoded, err := df.Encoded()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use a configuration that's compatible with the standard library
|
|
||||||
// to minimize the risk of introducing bugs. This will make sure
|
|
||||||
// that map keys is ordered.
|
|
||||||
jsonCfg := jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
return jsonCfg.Marshal(encoded)
|
|
||||||
}
|
|
@ -1,124 +0,0 @@
|
|||||||
package interval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
|
||||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultRes int64 = 1500
|
|
||||||
defaultMinInterval = time.Millisecond * 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type Interval struct {
|
|
||||||
Text string
|
|
||||||
Value time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type intervalCalculator struct {
|
|
||||||
minInterval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type Calculator interface {
|
|
||||||
Calculate(timeRange legacydata.DataTimeRange, interval time.Duration) Interval
|
|
||||||
CalculateSafeInterval(timeRange legacydata.DataTimeRange, resolution int64) Interval
|
|
||||||
}
|
|
||||||
|
|
||||||
type CalculatorOptions struct {
|
|
||||||
MinInterval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCalculator(opts ...CalculatorOptions) *intervalCalculator {
|
|
||||||
calc := &intervalCalculator{}
|
|
||||||
|
|
||||||
for _, o := range opts {
|
|
||||||
if o.MinInterval == 0 {
|
|
||||||
calc.minInterval = defaultMinInterval
|
|
||||||
} else {
|
|
||||||
calc.minInterval = o.MinInterval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return calc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Interval) Milliseconds() int64 {
|
|
||||||
return i.Value.Nanoseconds() / int64(time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *intervalCalculator) Calculate(timerange legacydata.DataTimeRange, minInterval time.Duration) Interval {
|
|
||||||
to := timerange.MustGetTo().UnixNano()
|
|
||||||
from := timerange.MustGetFrom().UnixNano()
|
|
||||||
calculatedInterval := time.Duration((to - from) / DefaultRes)
|
|
||||||
|
|
||||||
if calculatedInterval < minInterval {
|
|
||||||
return Interval{Text: gtime.FormatInterval(minInterval), Value: minInterval}
|
|
||||||
}
|
|
||||||
|
|
||||||
rounded := roundInterval(calculatedInterval)
|
|
||||||
return Interval{Text: gtime.FormatInterval(rounded), Value: rounded}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *intervalCalculator) CalculateSafeInterval(timerange legacydata.DataTimeRange, safeRes int64) Interval {
|
|
||||||
to := timerange.MustGetTo().UnixNano()
|
|
||||||
from := timerange.MustGetFrom().UnixNano()
|
|
||||||
safeInterval := time.Duration((to - from) / safeRes)
|
|
||||||
|
|
||||||
rounded := roundInterval(safeInterval)
|
|
||||||
return Interval{Text: gtime.FormatInterval(rounded), Value: rounded}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetIntervalFrom(dsInfo *datasources.DataSource, queryModel *simplejson.Json, defaultInterval time.Duration) (time.Duration, error) {
|
|
||||||
interval := queryModel.Get("interval").MustString("")
|
|
||||||
|
|
||||||
// intervalMs field appears in the v2 plugins API and should be preferred
|
|
||||||
// if 'interval' isn't present.
|
|
||||||
if interval == "" {
|
|
||||||
intervalMS := queryModel.Get("intervalMs").MustInt(0)
|
|
||||||
if intervalMS != 0 {
|
|
||||||
return time.Duration(intervalMS) * time.Millisecond, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if interval == "" && dsInfo != nil && dsInfo.JsonData != nil {
|
|
||||||
dsInterval := dsInfo.JsonData.Get("timeInterval").MustString("")
|
|
||||||
if dsInterval != "" {
|
|
||||||
interval = dsInterval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if interval == "" {
|
|
||||||
return defaultInterval, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
interval = strings.Replace(strings.Replace(interval, "<", "", 1), ">", "", 1)
|
|
||||||
isPureNum, err := regexp.MatchString(`^\d+$`, interval)
|
|
||||||
if err != nil {
|
|
||||||
return time.Duration(0), err
|
|
||||||
}
|
|
||||||
if isPureNum {
|
|
||||||
interval += "s"
|
|
||||||
}
|
|
||||||
parsedInterval, err := gtime.ParseDuration(interval)
|
|
||||||
if err != nil {
|
|
||||||
return time.Duration(0), err
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedInterval, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gocyclo
|
|
||||||
func roundInterval(interval time.Duration) time.Duration {
|
|
||||||
// 0.015s
|
|
||||||
if interval <= 15*time.Millisecond {
|
|
||||||
return time.Millisecond * 10 // 0.01s
|
|
||||||
}
|
|
||||||
return gtime.RoundInterval(interval)
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
package interval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
|
||||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIntervalCalculator_Calculate(t *testing.T) {
|
|
||||||
calculator := NewCalculator(CalculatorOptions{})
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
timeRange legacydata.DataTimeRange
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"from 5m to now", legacydata.NewDataTimeRange("5m", "now"), "200ms"},
|
|
||||||
{"from 15m to now", legacydata.NewDataTimeRange("15m", "now"), "500ms"},
|
|
||||||
{"from 30m to now", legacydata.NewDataTimeRange("30m", "now"), "1s"},
|
|
||||||
{"from 1h to now", legacydata.NewDataTimeRange("1h", "now"), "2s"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
interval := calculator.Calculate(tc.timeRange, time.Millisecond*1)
|
|
||||||
assert.Equal(t, tc.expected, interval.Text)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntervalCalculator_CalculateSafeInterval(t *testing.T) {
|
|
||||||
calculator := NewCalculator(CalculatorOptions{})
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
timeRange legacydata.DataTimeRange
|
|
||||||
safeResolution int64
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"from 5m to now", legacydata.NewDataTimeRange("5m", "now"), 11000, "20ms"},
|
|
||||||
{"from 15m to now", legacydata.NewDataTimeRange("15m", "now"), 11000, "100ms"},
|
|
||||||
{"from 30m to now", legacydata.NewDataTimeRange("30m", "now"), 11000, "200ms"},
|
|
||||||
{"from 24h to now", legacydata.NewDataTimeRange("24h", "now"), 11000, "10s"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
interval := calculator.CalculateSafeInterval(tc.timeRange, tc.safeResolution)
|
|
||||||
assert.Equal(t, tc.expected, interval.Text)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoundInterval(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
interval time.Duration
|
|
||||||
expected time.Duration
|
|
||||||
}{
|
|
||||||
{"30ms", time.Millisecond * 30, time.Millisecond * 20},
|
|
||||||
{"45ms", time.Millisecond * 45, time.Millisecond * 50},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, tc.expected, roundInterval(tc.interval))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetIntervalFrom(t *testing.T) {
|
|
||||||
dsJSON, err := simplejson.NewJson([]byte(`{"timeInterval": "60s"}`))
|
|
||||||
require.NoError(t, err)
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
dsInfo *datasources.DataSource
|
|
||||||
queryModel string
|
|
||||||
defaultInterval time.Duration
|
|
||||||
expected time.Duration
|
|
||||||
}{
|
|
||||||
{"45s", nil, `{"interval": "45s"}`, time.Second * 15, time.Second * 45},
|
|
||||||
{"45", nil, `{"interval": "45"}`, time.Second * 15, time.Second * 45},
|
|
||||||
{"2m", nil, `{"interval": "2m"}`, time.Second * 15, time.Minute * 2},
|
|
||||||
{"intervalMs", nil, `{"intervalMs": 45000}`, time.Second * 15, time.Second * 45},
|
|
||||||
{"intervalMs sub-seconds", nil, `{"intervalMs": 45200}`, time.Second * 15, time.Millisecond * 45200},
|
|
||||||
{"dsInfo timeInterval", &datasources.DataSource{
|
|
||||||
JsonData: dsJSON,
|
|
||||||
}, `{}`, time.Second * 15, time.Second * 60},
|
|
||||||
{"defaultInterval when interval empty", nil, `{"interval": ""}`, time.Second * 15, time.Second * 15},
|
|
||||||
{"defaultInterval when intervalMs 0", nil, `{"intervalMs": 0}`, time.Second * 15, time.Second * 15},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
js, _ := simplejson.NewJson([]byte(tc.queryModel))
|
|
||||||
actual, err := GetIntervalFrom(tc.dsInfo, js, tc.defaultInterval)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, tc.expected, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
|
||||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
|
||||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
pluginsClient plugins.Client
|
|
||||||
oAuthTokenService oauthtoken.OAuthTokenService
|
|
||||||
dataSourcesService datasources.DataSourceService
|
|
||||||
pCtxProvider *plugincontext.Provider
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProvideService(pluginsClient plugins.Client, oAuthTokenService oauthtoken.OAuthTokenService,
|
|
||||||
dataSourcesService datasources.DataSourceService, pCtxProvider *plugincontext.Provider) *Service {
|
|
||||||
return &Service{
|
|
||||||
pluginsClient: pluginsClient,
|
|
||||||
oAuthTokenService: oAuthTokenService,
|
|
||||||
dataSourcesService: dataSourcesService,
|
|
||||||
pCtxProvider: pCtxProvider,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:staticcheck // legacydata.DataResponse deprecated
|
|
||||||
func (h *Service) HandleRequest(ctx context.Context, ds *datasources.DataSource, query legacydata.DataQuery) (legacydata.DataResponse, error) {
|
|
||||||
req, err := h.generateRequest(ctx, ds, query)
|
|
||||||
if err != nil {
|
|
||||||
return legacydata.DataResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.pluginsClient.QueryData(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return legacydata.DataResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tR := legacydata.DataResponse{
|
|
||||||
Results: make(map[string]legacydata.DataQueryResult, len(resp.Responses)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for refID, r := range resp.Responses {
|
|
||||||
qr := legacydata.DataQueryResult{
|
|
||||||
RefID: refID,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range r.Frames {
|
|
||||||
if f.RefID == "" {
|
|
||||||
f.RefID = refID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qr.Dataframes = legacydata.NewDecodedDataFrames(r.Frames)
|
|
||||||
|
|
||||||
if r.Error != nil {
|
|
||||||
qr.Error = r.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
tR.Results[refID] = qr
|
|
||||||
}
|
|
||||||
|
|
||||||
return tR, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Service) generateRequest(ctx context.Context, ds *datasources.DataSource, query legacydata.DataQuery) (*backend.QueryDataRequest, error) {
|
|
||||||
if query.Headers == nil {
|
|
||||||
query.Headers = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
pCtx, err := h.pCtxProvider.GetWithDataSource(ctx, ds.Type, query.User, ds)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &backend.QueryDataRequest{
|
|
||||||
PluginContext: pCtx,
|
|
||||||
Queries: []backend.DataQuery{},
|
|
||||||
Headers: query.Headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, q := range query.Queries {
|
|
||||||
modelJSON, err := q.Model.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Queries = append(req.Queries, backend.DataQuery{
|
|
||||||
RefID: q.RefID,
|
|
||||||
Interval: time.Duration(q.IntervalMS) * time.Millisecond,
|
|
||||||
MaxDataPoints: q.MaxDataPoints,
|
|
||||||
TimeRange: backend.TimeRange{
|
|
||||||
From: query.TimeRange.GetFromAsTimeUTC(),
|
|
||||||
To: query.TimeRange.GetToAsTimeUTC(),
|
|
||||||
},
|
|
||||||
QueryType: q.QueryType,
|
|
||||||
JSON: modelJSON,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ legacydata.RequestHandler = &Service{}
|
|
@ -1,89 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
|
||||||
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
|
||||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
|
||||||
"github.com/grafana/grafana/pkg/services/datasources/guardian"
|
|
||||||
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
|
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig"
|
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
|
||||||
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service"
|
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
|
||||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
|
||||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
|
||||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
|
||||||
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
|
||||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
testsuite.Run(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleRequest(t *testing.T) {
|
|
||||||
t.Run("Should invoke plugin manager QueryData when handling request for query", func(t *testing.T) {
|
|
||||||
client := &fakePluginsClient{}
|
|
||||||
var actualReq *backend.QueryDataRequest
|
|
||||||
client.QueryDataHandlerFunc = func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
|
||||||
actualReq = req
|
|
||||||
return backend.NewQueryDataResponse(), nil
|
|
||||||
}
|
|
||||||
sqlStore, cfg := db.InitTestDBWithCfg(t)
|
|
||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
|
||||||
datasourcePermissions := acmock.NewMockedPermissionsService()
|
|
||||||
quotaService := quotatest.New(false, nil)
|
|
||||||
dsCache := datasourceservice.ProvideCacheService(localcache.ProvideService(), sqlStore, guardian.ProvideGuardian())
|
|
||||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(),
|
|
||||||
acmock.New(), datasourcePermissions, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
|
||||||
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
pCtxProvider := plugincontext.ProvideService(cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{
|
|
||||||
PluginList: []pluginstore.Plugin{{JSONData: plugins.JSONData{ID: "test"}}},
|
|
||||||
}, dsCache, dsService, pluginSettings.ProvideService(sqlStore, secretsService), pluginconfig.NewFakePluginRequestConfigProvider())
|
|
||||||
s := ProvideService(client, nil, dsService, pCtxProvider)
|
|
||||||
|
|
||||||
ds := &datasources.DataSource{ID: 12, Type: "test", JsonData: simplejson.New()}
|
|
||||||
req := legacydata.DataQuery{
|
|
||||||
TimeRange: &legacydata.DataTimeRange{},
|
|
||||||
Queries: []legacydata.DataSubQuery{
|
|
||||||
{RefID: "A", Model: simplejson.New()},
|
|
||||||
{RefID: "B", Model: simplejson.New()},
|
|
||||||
},
|
|
||||||
User: &user.SignedInUser{},
|
|
||||||
}
|
|
||||||
res, err := s.HandleRequest(context.Background(), ds, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, actualReq)
|
|
||||||
require.NotNil(t, res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakePluginsClient struct {
|
|
||||||
plugins.Client
|
|
||||||
backend.QueryDataHandlerFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *fakePluginsClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
|
||||||
if m.QueryDataHandlerFunc != nil {
|
|
||||||
return m.QueryDataHandlerFunc.QueryData(ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user