mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana: include a built-in backend datasource (#38571)
This commit is contained in:
parent
f74421b892
commit
6bda64cb19
@ -370,7 +370,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// metrics
|
||||
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetrics))
|
||||
apiRoute.Get("/tsdb/testdata/random-walk", routing.Wrap(hs.GetTestDataRandomWalk))
|
||||
|
||||
// DataSource w/ expressions
|
||||
apiRoute.Post("/ds/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetricsV2))
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -112,11 +113,16 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins *plu
|
||||
// the datasource table)
|
||||
for _, ds := range hs.PluginManager.DataSources() {
|
||||
if ds.BuiltIn {
|
||||
dataSources[ds.Name] = map[string]interface{}{
|
||||
info := map[string]interface{}{
|
||||
"type": ds.Type,
|
||||
"name": ds.Name,
|
||||
"meta": hs.PluginManager.GetDataSource(ds.Id),
|
||||
}
|
||||
if ds.Name == grafanads.DatasourceName {
|
||||
info["id"] = grafanads.DatasourceID
|
||||
info["uid"] = grafanads.DatasourceUID
|
||||
}
|
||||
dataSources[ds.Name] = info
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// QueryMetricsV2 returns query metrics.
|
||||
@ -31,31 +30,42 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricReq
|
||||
}
|
||||
|
||||
// Loop to see if we have an expression.
|
||||
prevType := ""
|
||||
var ds *models.DataSource
|
||||
for _, query := range reqDTO.Queries {
|
||||
if query.Get("datasource").MustString("") == expr.DatasourceName {
|
||||
dsType := query.Get("datasource").MustString("")
|
||||
if dsType == expr.DatasourceName {
|
||||
return hs.handleExpressions(c, reqDTO)
|
||||
}
|
||||
}
|
||||
|
||||
var ds *models.DataSource
|
||||
for i, query := range reqDTO.Queries {
|
||||
hs.log.Debug("Processing metrics query", "query", query)
|
||||
|
||||
datasourceID, err := query.Get("datasourceId").Int64()
|
||||
if err != nil {
|
||||
if prevType != "" && prevType != dsType {
|
||||
// For mixed datasource case, each data source is sent in a single request.
|
||||
// So only the datasource from the first query is needed. As all requests
|
||||
// should be the same data source.
|
||||
hs.log.Debug("Can't process query since it's missing data source ID")
|
||||
return response.Error(http.StatusBadRequest, "Query missing data source ID", nil)
|
||||
return response.Error(http.StatusBadRequest, "All queries must use the same datasource", nil)
|
||||
}
|
||||
|
||||
// For mixed datasource case, each data source is sent in a single request.
|
||||
// So only the datasource from the first query is needed. As all requests
|
||||
// should be the same data source.
|
||||
if i == 0 {
|
||||
ds, err = hs.DataSourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache)
|
||||
if ds == nil {
|
||||
// require ID for everything
|
||||
dsID, err := query.Get("datasourceId").Int64()
|
||||
if err != nil {
|
||||
return hs.handleGetDataSourceError(err, datasourceID)
|
||||
hs.log.Debug("Can't process query since it's missing data source ID")
|
||||
return response.Error(http.StatusBadRequest, "Query missing data source ID", nil)
|
||||
}
|
||||
if dsID == grafanads.DatasourceID {
|
||||
ds = grafanads.DataSourceModel(c.OrgId)
|
||||
} else {
|
||||
ds, err = hs.DataSourceCache.GetDatasource(dsID, c.SignedInUser, c.SkipCache)
|
||||
if err != nil {
|
||||
return hs.handleGetDataSourceError(err, dsID)
|
||||
}
|
||||
}
|
||||
}
|
||||
prevType = dsType
|
||||
}
|
||||
|
||||
for _, query := range reqDTO.Queries {
|
||||
hs.log.Debug("Processing metrics query", "query", query)
|
||||
|
||||
request.Queries = append(request.Queries, plugins.DataSubQuery{
|
||||
RefID: query.Get("refId").MustString("A"),
|
||||
@ -212,37 +222,3 @@ func (hs *HTTPServer) QueryMetrics(c *models.ReqContext, reqDto dtos.MetricReque
|
||||
|
||||
return response.JSON(statusCode, &resp)
|
||||
}
|
||||
|
||||
// GET /api/tsdb/testdata/random-walk
|
||||
func (hs *HTTPServer) GetTestDataRandomWalk(c *models.ReqContext) response.Response {
|
||||
from := c.Query("from")
|
||||
to := c.Query("to")
|
||||
intervalMS := c.QueryInt64("intervalMs")
|
||||
|
||||
timeRange := plugins.NewDataTimeRange(from, to)
|
||||
request := plugins.DataQuery{TimeRange: &timeRange}
|
||||
|
||||
dsInfo := &models.DataSource{
|
||||
Type: "testdata",
|
||||
JsonData: simplejson.New(),
|
||||
}
|
||||
request.Queries = append(request.Queries, plugins.DataSubQuery{
|
||||
RefID: "A",
|
||||
IntervalMS: intervalMS,
|
||||
Model: simplejson.NewFromAny(&util.DynMap{
|
||||
"scenario": "random_walk",
|
||||
}),
|
||||
DataSource: dsInfo,
|
||||
})
|
||||
|
||||
resp, err := hs.DataService.HandleRequest(context.Background(), dsInfo, request)
|
||||
if err != nil {
|
||||
return response.Error(500, "Metric request error", err)
|
||||
}
|
||||
|
||||
qdr, err := resp.ToBackendDataResponse()
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "error converting results", err)
|
||||
}
|
||||
return toMacronResponse(qdr)
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/elasticsearch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
"github.com/grafana/grafana/pkg/tsdb/graphite"
|
||||
"github.com/grafana/grafana/pkg/tsdb/influxdb"
|
||||
"github.com/grafana/grafana/pkg/tsdb/loki"
|
||||
@ -46,7 +47,7 @@ func ProvideBackgroundServiceRegistry(
|
||||
_ *azuremonitor.Service, _ *cloudwatch.CloudWatchService, _ *elasticsearch.Service, _ *graphite.Service,
|
||||
_ *influxdb.Service, _ *loki.Service, _ *opentsdb.Service, _ *prometheus.Service, _ *tempo.Service,
|
||||
_ *testdatasource.TestDataPlugin, _ *plugindashboards.Service, _ *dashboardsnapshots.Service,
|
||||
_ *postgres.Service, _ *mysql.Service, _ *mssql.Service,
|
||||
_ *postgres.Service, _ *mysql.Service, _ *mssql.Service, _ *grafanads.Service,
|
||||
|
||||
) *BackgroundServiceRegistry {
|
||||
return NewBackgroundServiceRegistry(
|
||||
|
@ -57,6 +57,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudmonitoring"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/elasticsearch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
"github.com/grafana/grafana/pkg/tsdb/graphite"
|
||||
"github.com/grafana/grafana/pkg/tsdb/influxdb"
|
||||
"github.com/grafana/grafana/pkg/tsdb/loki"
|
||||
@ -141,6 +142,7 @@ var wireBasicSet = wire.NewSet(
|
||||
graphite.ProvideService,
|
||||
prometheus.ProvideService,
|
||||
elasticsearch.ProvideService,
|
||||
grafanads.ProvideService,
|
||||
dashboardsnapshots.ProvideService,
|
||||
)
|
||||
|
||||
|
230
pkg/tsdb/grafanads/grafana.go
Normal file
230
pkg/tsdb/grafanads/grafana.go
Normal file
@ -0,0 +1,230 @@
|
||||
package grafanads
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/securejsondata"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/testdatasource"
|
||||
)
|
||||
|
||||
// DatasourceName is the string constant used as the datasource name in requests
|
||||
// to identify it as a Grafana DS command.
|
||||
const DatasourceName = "-- Grafana --"
|
||||
|
||||
// DatasourceID is the fake datasource id used in requests to identify it as a
|
||||
// Grafana DS command.
|
||||
const DatasourceID = -1
|
||||
|
||||
// DatasourceUID is the fake datasource uid used in requests to identify it as a
|
||||
// Grafana DS command.
|
||||
const DatasourceUID = "grafana"
|
||||
|
||||
// Make sure Service implements required interfaces.
|
||||
// This is important to do since otherwise we will only get a
|
||||
// not implemented error response from plugin at runtime.
|
||||
var (
|
||||
_ backend.QueryDataHandler = (*Service)(nil)
|
||||
_ backend.CheckHealthHandler = (*Service)(nil)
|
||||
logger = log.New("tsdb.grafana")
|
||||
)
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, backendPM backendplugin.Manager) *Service {
|
||||
return newService(cfg.StaticRootPath, backendPM)
|
||||
}
|
||||
|
||||
func newService(staticRootPath string, backendPM backendplugin.Manager) *Service {
|
||||
s := &Service{
|
||||
staticRootPath: staticRootPath,
|
||||
roots: []string{
|
||||
"testdata",
|
||||
"img/icons",
|
||||
"img/bg",
|
||||
"gazetteer",
|
||||
"upload", // does not exist yet
|
||||
},
|
||||
}
|
||||
|
||||
if err := backendPM.Register("grafana", coreplugin.New(backend.ServeOpts{
|
||||
CheckHealthHandler: s,
|
||||
QueryDataHandler: s,
|
||||
})); err != nil {
|
||||
logger.Error("Failed to register plugin", "error", err)
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Service exists regardless of user settings
|
||||
type Service struct {
|
||||
// path to the public folder
|
||||
staticRootPath string
|
||||
roots []string
|
||||
}
|
||||
|
||||
func DataSourceModel(orgId int64) *models.DataSource {
|
||||
return &models.DataSource{
|
||||
Id: DatasourceID,
|
||||
Uid: DatasourceUID,
|
||||
Name: DatasourceName,
|
||||
Type: "grafana",
|
||||
OrgId: orgId,
|
||||
JsonData: simplejson.New(),
|
||||
SecureJsonData: make(securejsondata.SecureJsonData),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) QueryData(_ context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
response := backend.NewQueryDataResponse()
|
||||
|
||||
for _, q := range req.Queries {
|
||||
switch q.QueryType {
|
||||
case queryTypeRandomWalk:
|
||||
response.Responses[q.RefID] = s.doRandomWalk(q)
|
||||
case queryTypeList:
|
||||
response.Responses[q.RefID] = s.doListQuery(q)
|
||||
case queryTypeRead:
|
||||
response.Responses[q.RefID] = s.doReadQuery(q)
|
||||
default:
|
||||
response.Responses[q.RefID] = backend.DataResponse{
|
||||
Error: fmt.Errorf("unknown query type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Service) CheckHealth(_ context.Context, _ *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusOk,
|
||||
Message: "OK",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) publicPath(path string) (string, error) {
|
||||
if strings.Contains(path, "..") {
|
||||
return "", fmt.Errorf("invalid string")
|
||||
}
|
||||
|
||||
ok := false
|
||||
for _, root := range s.roots {
|
||||
if strings.HasPrefix(path, root) {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return "", fmt.Errorf("bad root path")
|
||||
}
|
||||
return filepath.Join(s.staticRootPath, path), nil
|
||||
}
|
||||
|
||||
func (s *Service) doListQuery(query backend.DataQuery) backend.DataResponse {
|
||||
q := &listQueryModel{}
|
||||
response := backend.DataResponse{}
|
||||
err := json.Unmarshal(query.JSON, &q)
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
|
||||
if q.Path == "" {
|
||||
count := len(s.roots)
|
||||
names := data.NewFieldFromFieldType(data.FieldTypeString, count)
|
||||
mtype := data.NewFieldFromFieldType(data.FieldTypeString, count)
|
||||
names.Name = "name"
|
||||
mtype.Name = "mediaType"
|
||||
for i, f := range s.roots {
|
||||
names.Set(i, f)
|
||||
mtype.Set(i, "directory")
|
||||
}
|
||||
frame := data.NewFrame("", names, mtype)
|
||||
frame.SetMeta(&data.FrameMeta{
|
||||
Type: data.FrameTypeDirectoryListing,
|
||||
})
|
||||
response.Frames = data.Frames{frame}
|
||||
} else {
|
||||
path, err := s.publicPath(q.Path)
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
frame, err := experimental.GetDirectoryFrame(path, false)
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
response.Frames = data.Frames{frame}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func (s *Service) doReadQuery(query backend.DataQuery) backend.DataResponse {
|
||||
q := &listQueryModel{}
|
||||
response := backend.DataResponse{}
|
||||
err := json.Unmarshal(query.JSON, &q)
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
|
||||
if filepath.Ext(q.Path) != ".csv" {
|
||||
response.Error = fmt.Errorf("unsupported file type")
|
||||
return response
|
||||
}
|
||||
|
||||
path, err := s.publicPath(q.Path)
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
|
||||
// Can ignore gosec G304 here, because we check the file pattern above
|
||||
// nolint:gosec
|
||||
fileReader, err := os.Open(path)
|
||||
if err != nil {
|
||||
response.Error = fmt.Errorf("failed to read file")
|
||||
return response
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := fileReader.Close(); err != nil {
|
||||
logger.Warn("Failed to close file", "err", err, "path", path)
|
||||
}
|
||||
}()
|
||||
|
||||
frame, err := testdatasource.LoadCsvContent(fileReader, filepath.Base(path))
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
response.Frames = data.Frames{frame}
|
||||
return response
|
||||
}
|
||||
|
||||
func (s *Service) doRandomWalk(query backend.DataQuery) backend.DataResponse {
|
||||
response := backend.DataResponse{}
|
||||
|
||||
model := simplejson.New()
|
||||
response.Frames = data.Frames{testdatasource.RandomWalk(query, model, 0)}
|
||||
|
||||
return response
|
||||
}
|
50
pkg/tsdb/grafanads/grafana_test.go
Normal file
50
pkg/tsdb/grafanads/grafana_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
package grafanads
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func asJSON(v interface{}) json.RawMessage {
|
||||
b, _ := json.Marshal(v)
|
||||
return b
|
||||
}
|
||||
|
||||
func TestReadFolderListing(t *testing.T) {
|
||||
ds := newService("../../../public", &fakeBackendPM{})
|
||||
dr := ds.doListQuery(backend.DataQuery{
|
||||
QueryType: "x",
|
||||
JSON: asJSON(listQueryModel{
|
||||
Path: "testdata",
|
||||
}),
|
||||
})
|
||||
err := experimental.CheckGoldenDataResponse(path.Join("testdata", "list.golden.txt"), &dr, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestReadCSVFile(t *testing.T) {
|
||||
ds := newService("../../../public", &fakeBackendPM{})
|
||||
dr := ds.doReadQuery(backend.DataQuery{
|
||||
QueryType: "x",
|
||||
JSON: asJSON(readQueryModel{
|
||||
Path: "testdata/js_libraries.csv",
|
||||
}),
|
||||
})
|
||||
err := experimental.CheckGoldenDataResponse(path.Join("testdata", "jslib.golden.txt"), &dr, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
type fakeBackendPM struct {
|
||||
backendplugin.Manager
|
||||
}
|
||||
|
||||
func (pm *fakeBackendPM) Register(pluginID string, factory backendplugin.PluginFactoryFunc) error {
|
||||
return nil
|
||||
}
|
21
pkg/tsdb/grafanads/query.go
Normal file
21
pkg/tsdb/grafanads/query.go
Normal file
@ -0,0 +1,21 @@
|
||||
package grafanads
|
||||
|
||||
const (
|
||||
// QueryTypeRandomWalk returns a random walk series
|
||||
queryTypeRandomWalk = "randomWalk"
|
||||
|
||||
// QueryTypeList will list the files in a folder
|
||||
queryTypeList = "list"
|
||||
|
||||
// QueryTypeRead will read a file and return it as data frames
|
||||
// currently only .csv files are supported,
|
||||
// other file types will eventually be supported (parquet, etc)
|
||||
queryTypeRead = "read"
|
||||
)
|
||||
|
||||
type listQueryModel struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
type readQueryModel struct {
|
||||
Path string `json:"path"`
|
||||
}
|
21
pkg/tsdb/grafanads/testdata/jslib.golden.txt
vendored
Normal file
21
pkg/tsdb/grafanads/testdata/jslib.golden.txt
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
🌟 This was machine generated. Do not edit. 🌟
|
||||
|
||||
Frame[0]
|
||||
Name: js_libraries.csv
|
||||
Dimensions: 4 Fields by 6 Rows
|
||||
+-----------------+--------------------+----------------+----------------+
|
||||
| Name: Library | Name: Github Stars | Name: Forks | Name: Watchers |
|
||||
| Labels: | Labels: | Labels: | Labels: |
|
||||
| Type: []*string | Type: []*int64 | Type: []*int64 | Type: []*int64 |
|
||||
+-----------------+--------------------+----------------+----------------+
|
||||
| React.js | 169000 | 34000 | 6700 |
|
||||
| Vue | 184000 | 29100 | 6300 |
|
||||
| Angular | 73400 | 19300 | 3200 |
|
||||
| JQuery | 54900 | 20000 | 3300 |
|
||||
| Meteor | 42400 | 5200 | 1700 |
|
||||
| Aurelia | 11600 | 684 | 442 |
|
||||
+-----------------+--------------------+----------------+----------------+
|
||||
|
||||
|
||||
====== TEST DATA RESPONSE (arrow base64) ======
|
||||
FRAME=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
|
24
pkg/tsdb/grafanads/testdata/list.golden.txt
vendored
Normal file
24
pkg/tsdb/grafanads/testdata/list.golden.txt
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
🌟 This was machine generated. Do not edit. 🌟
|
||||
|
||||
Frame[0] {
|
||||
"type": "directory-listing",
|
||||
"pathSeparator": "/"
|
||||
}
|
||||
Name:
|
||||
Dimensions: 2 Fields by 6 Rows
|
||||
+--------------------------+------------------+
|
||||
| Name: name | Name: media-type |
|
||||
| Labels: | Labels: |
|
||||
| Type: []string | Type: []string |
|
||||
+--------------------------+------------------+
|
||||
| browser_marketshare.csv | |
|
||||
| flight_info_by_state.csv | |
|
||||
| gdp_per_capita.csv | |
|
||||
| js_libraries.csv | |
|
||||
| population_by_state.csv | |
|
||||
| weight_height.csv | |
|
||||
+--------------------------+------------------+
|
||||
|
||||
|
||||
====== TEST DATA RESPONSE (arrow base64) ======
|
||||
FRAME=QVJST1cxAAD/////uAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAKQAAAADAAAATAAAACgAAAAEAAAA0P7//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADw/v//CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAABD///8IAAAAPAAAADAAAAB7InR5cGUiOiJkaXJlY3RvcnktbGlzdGluZyIsInBhdGhTZXBhcmF0b3IiOiIvIn0AAAAABAAAAG1ldGEAAAAAAgAAAHwAAAAEAAAAnv///xQAAABAAAAAQAAAAAAAAAU8AAAAAQAAAAQAAACM////CAAAABQAAAAKAAAAbWVkaWEtdHlwZQAABAAAAG5hbWUAAAAAAAAAAIj///8KAAAAbWVkaWEtdHlwZQAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAEQAAABIAAAAAAAABUQAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABAAAAG5hbWUAAAAABAAAAG5hbWUAAAAAAAAAAAQABAAEAAAABAAAAG5hbWUAAAAA/////9gAAAAUAAAAAAAAAAwAFgAUABMADAAEAAwAAADAAAAAAAAAABQAAAAAAAADAwAKABgADAAIAAQACgAAABQAAAB4AAAABgAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAACAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAACAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAFwAAAC8AAABBAAAAUQAAAGgAAAB5AAAAAAAAAGJyb3dzZXJfbWFya2V0c2hhcmUuY3N2ZmxpZ2h0X2luZm9fYnlfc3RhdGUuY3N2Z2RwX3Blcl9jYXBpdGEuY3N2anNfbGlicmFyaWVzLmNzdnBvcHVsYXRpb25fYnlfc3RhdGUuY3N2d2VpZ2h0X2hlaWdodC5jc3YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAPAAAAAAAAwABAAAAyAEAAAAAAADgAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAwAAAAIAAQACgAAAAgAAACkAAAAAwAAAEwAAAAoAAAABAAAAND+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAA8P7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAAAQ////CAAAADwAAAAwAAAAeyJ0eXBlIjoiZGlyZWN0b3J5LWxpc3RpbmciLCJwYXRoU2VwYXJhdG9yIjoiLyJ9AAAAAAQAAABtZXRhAAAAAAIAAAB8AAAABAAAAJ7///8UAAAAQAAAAEAAAAAAAAAFPAAAAAEAAAAEAAAAjP///wgAAAAUAAAACgAAAG1lZGlhLXR5cGUAAAQAAABuYW1lAAAAAAAAAACI////CgAAAG1lZGlhLXR5cGUAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAASAAAAAAAAAVEAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABuYW1lAAAAAAQAAABuYW1lAAAAAAAAAAAEAAQABAAAAAQAAABuYW1lAAAAAOgBAABBUlJPVzE=
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
@ -16,8 +15,11 @@ import (
|
||||
|
||||
// NewService returns a new Service.
|
||||
func NewService(
|
||||
cfg *setting.Cfg, pluginManager plugins.Manager, backendPluginManager backendplugin.Manager,
|
||||
oauthTokenService *oauthtoken.Service, httpClientProvider httpclient.Provider, cloudMonitoringService *cloudmonitoring.Service,
|
||||
cfg *setting.Cfg,
|
||||
pluginManager plugins.Manager,
|
||||
backendPluginManager backendplugin.Manager,
|
||||
oauthTokenService *oauthtoken.Service,
|
||||
cloudMonitoringService *cloudmonitoring.Service,
|
||||
) *Service {
|
||||
s := newService(cfg, pluginManager, backendPluginManager, oauthTokenService)
|
||||
|
||||
@ -46,7 +48,6 @@ type Service struct {
|
||||
PluginManager plugins.Manager
|
||||
BackendPluginManager backendplugin.Manager
|
||||
OAuthTokenService oauthtoken.OAuthTokenService
|
||||
|
||||
//nolint: staticcheck // plugins.DataPlugin deprecated
|
||||
registry map[string]func(*models.DataSource) (plugins.DataPlugin, error)
|
||||
}
|
||||
@ -64,7 +65,6 @@ func (s *Service) HandleRequest(ctx context.Context, ds *models.DataSource, quer
|
||||
|
||||
return plugin.DataQuery(ctx, ds, query)
|
||||
}
|
||||
|
||||
return dataPluginQueryAdapter(ds.Type, s.BackendPluginManager, s.OAuthTokenService).DataQuery(ctx, ds, query)
|
||||
}
|
||||
|
||||
|
@ -142,6 +142,7 @@ func createService() (*Service, *fakeExecutor, *fakeBackendPM) {
|
||||
manager := &manager.PluginManager{
|
||||
BackendPluginManager: fakeBackendPM,
|
||||
}
|
||||
|
||||
s := newService(setting.NewCfg(), manager, fakeBackendPM, &fakeOAuthTokenService{})
|
||||
e := &fakeExecutor{
|
||||
//nolint: staticcheck // plugins.DataPlugin deprecated
|
||||
|
@ -30,7 +30,7 @@ func (p *TestDataPlugin) handleCsvContentScenario(ctx context.Context, req *back
|
||||
csvContent := model.Get("csvContent").MustString()
|
||||
alias := model.Get("alias").MustString("")
|
||||
|
||||
frame, err := p.loadCsvContent(strings.NewReader(csvContent), alias)
|
||||
frame, err := LoadCsvContent(strings.NewReader(csvContent), alias)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -94,10 +94,11 @@ func (p *TestDataPlugin) loadCsvFile(fileName string) (*data.Frame, error) {
|
||||
}
|
||||
}()
|
||||
|
||||
return p.loadCsvContent(fileReader, fileName)
|
||||
return LoadCsvContent(fileReader, fileName)
|
||||
}
|
||||
|
||||
func (p *TestDataPlugin) loadCsvContent(ioReader io.Reader, name string) (*data.Frame, error) {
|
||||
// LoadCsvContent should be moved to the SDK
|
||||
func LoadCsvContent(ioReader io.Reader, name string) (*data.Frame, error) {
|
||||
reader := csv.NewReader(ioReader)
|
||||
|
||||
// Read the header records
|
||||
|
@ -35,7 +35,7 @@ func TestCSVFileScenario(t *testing.T) {
|
||||
_ = fileReader.Close()
|
||||
}()
|
||||
|
||||
frame, err := p.loadCsvContent(fileReader, name)
|
||||
frame, err := LoadCsvContent(fileReader, name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, frame)
|
||||
|
||||
|
@ -268,7 +268,7 @@ func (p *TestDataPlugin) handleRandomWalkScenario(ctx context.Context, req *back
|
||||
|
||||
for i := 0; i < seriesCount; i++ {
|
||||
respD := resp.Responses[q.RefID]
|
||||
respD.Frames = append(respD.Frames, randomWalk(q, model, i))
|
||||
respD.Frames = append(respD.Frames, RandomWalk(q, model, i))
|
||||
resp.Responses[q.RefID] = respD
|
||||
}
|
||||
}
|
||||
@ -354,7 +354,7 @@ func (p *TestDataPlugin) handleRandomWalkWithErrorScenario(ctx context.Context,
|
||||
}
|
||||
|
||||
respD := resp.Responses[q.RefID]
|
||||
respD.Frames = append(respD.Frames, randomWalk(q, model, 0))
|
||||
respD.Frames = append(respD.Frames, RandomWalk(q, model, 0))
|
||||
respD.Error = fmt.Errorf("this is an error and it can include URLs http://grafana.com/")
|
||||
resp.Responses[q.RefID] = respD
|
||||
}
|
||||
@ -376,7 +376,7 @@ func (p *TestDataPlugin) handleRandomWalkSlowScenario(ctx context.Context, req *
|
||||
time.Sleep(parsedInterval)
|
||||
|
||||
respD := resp.Responses[q.RefID]
|
||||
respD.Frames = append(respD.Frames, randomWalk(q, model, 0))
|
||||
respD.Frames = append(respD.Frames, RandomWalk(q, model, 0))
|
||||
resp.Responses[q.RefID] = respD
|
||||
}
|
||||
|
||||
@ -618,7 +618,7 @@ func (p *TestDataPlugin) handleLogsScenario(ctx context.Context, req *backend.Qu
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func randomWalk(query backend.DataQuery, model *simplejson.Json, index int) *data.Frame {
|
||||
func RandomWalk(query backend.DataQuery, model *simplejson.Json, index int) *data.Frame {
|
||||
timeWalkerMs := query.TimeRange.From.UnixNano() / int64(time.Millisecond)
|
||||
to := query.TimeRange.To.UnixNano() / int64(time.Millisecond)
|
||||
startValue := model.Get("startValue").MustFloat64(rand.Float64() * 100)
|
||||
|
@ -1,9 +1,16 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { InlineField, Select, Alert, Input } from '@grafana/ui';
|
||||
import { QueryEditorProps, SelectableValue, dataFrameFromJSON, rangeUtil } from '@grafana/data';
|
||||
import { InlineField, Select, Alert, Input, InlineFieldRow } from '@grafana/ui';
|
||||
import {
|
||||
QueryEditorProps,
|
||||
SelectableValue,
|
||||
dataFrameFromJSON,
|
||||
rangeUtil,
|
||||
DataQueryRequest,
|
||||
DataFrame,
|
||||
} from '@grafana/data';
|
||||
import { GrafanaDatasource } from '../datasource';
|
||||
import { defaultQuery, GrafanaQuery, GrafanaQueryType } from '../types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
type Props = QueryEditorProps<GrafanaDatasource, GrafanaQuery>;
|
||||
|
||||
@ -12,6 +19,7 @@ const labelWidth = 12;
|
||||
interface State {
|
||||
channels: Array<SelectableValue<string>>;
|
||||
channelFields: Record<string, Array<SelectableValue<string>>>;
|
||||
folders?: Array<SelectableValue<string>>;
|
||||
}
|
||||
|
||||
export class QueryEditor extends PureComponent<Props, State> {
|
||||
@ -28,6 +36,11 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
value: GrafanaQueryType.LiveMeasurements,
|
||||
description: 'Stream real-time measurements from Grafana',
|
||||
},
|
||||
{
|
||||
label: 'List public files',
|
||||
value: GrafanaQueryType.List,
|
||||
description: 'Show directory listings for public resources',
|
||||
},
|
||||
];
|
||||
|
||||
loadChannelInfo() {
|
||||
@ -62,6 +75,30 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
loadFolderInfo() {
|
||||
const query: DataQueryRequest<GrafanaQuery> = {
|
||||
targets: [{ queryType: GrafanaQueryType.List, refId: 'A' }],
|
||||
} as any;
|
||||
|
||||
getDataSourceSrv()
|
||||
.get('-- Grafana --')
|
||||
.then((ds) => {
|
||||
const gds = ds as GrafanaDatasource;
|
||||
gds.query(query).subscribe({
|
||||
next: (rsp) => {
|
||||
if (rsp.data.length) {
|
||||
const names = (rsp.data[0] as DataFrame).fields[0];
|
||||
const folders = names.values.toArray().map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
}));
|
||||
this.setState({ folders });
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadChannelInfo();
|
||||
}
|
||||
@ -242,6 +279,49 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
onFolderChanged = (sel: SelectableValue<string>) => {
|
||||
const { onChange, query, onRunQuery } = this.props;
|
||||
onChange({ ...query, path: sel?.value });
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
renderListPublicFiles() {
|
||||
let { path } = this.props.query;
|
||||
let { folders } = this.state;
|
||||
if (!folders) {
|
||||
folders = [];
|
||||
this.loadFolderInfo();
|
||||
}
|
||||
const currentFolder = folders.find((f) => f.value === path);
|
||||
if (path && !currentFolder) {
|
||||
folders = [
|
||||
...folders,
|
||||
{
|
||||
value: path,
|
||||
label: path,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Path" grow={true} labelWidth={labelWidth}>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
options={folders}
|
||||
value={currentFolder || ''}
|
||||
onChange={this.onFolderChanged}
|
||||
allowCustomValue={true}
|
||||
backspaceRemovesValue={true}
|
||||
placeholder="Select folder"
|
||||
isClearable={true}
|
||||
formatCreateLabel={(input: string) => `Folder: ${input}`}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const query = {
|
||||
...defaultQuery,
|
||||
@ -250,7 +330,7 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Query type" grow={true} labelWidth={labelWidth}>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
@ -259,8 +339,9 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
onChange={this.onQueryTypeChange}
|
||||
/>
|
||||
</InlineField>
|
||||
</div>
|
||||
</InlineFieldRow>
|
||||
{query.queryType === GrafanaQueryType.LiveMeasurements && this.renderMeasurementsQuery()}
|
||||
{query.queryType === GrafanaQueryType.List && this.renderListPublicFiles()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { from, merge, Observable, of } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { getBackendSrv, getGrafanaLiveSrv, getTemplateSrv, toDataQueryResponse } from '@grafana/runtime';
|
||||
import { DataSourceWithBackend, getBackendSrv, getGrafanaLiveSrv, getTemplateSrv } from '@grafana/runtime';
|
||||
import {
|
||||
AnnotationQuery,
|
||||
AnnotationQueryRequest,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
DataSourceInstanceSettings,
|
||||
DatasourceRef,
|
||||
isValidLiveChannelAddress,
|
||||
@ -22,7 +20,7 @@ import { isString } from 'lodash';
|
||||
|
||||
let counter = 100;
|
||||
|
||||
export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
|
||||
constructor(instanceSettings: DataSourceInstanceSettings) {
|
||||
super(instanceSettings);
|
||||
this.annotations = {
|
||||
@ -49,7 +47,8 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
}
|
||||
|
||||
query(request: DataQueryRequest<GrafanaQuery>): Observable<DataQueryResponse> {
|
||||
const queries: Array<Observable<DataQueryResponse>> = [];
|
||||
const results: Array<Observable<DataQueryResponse>> = [];
|
||||
const targets: GrafanaQuery[] = [];
|
||||
const templateSrv = getTemplateSrv();
|
||||
for (const target of request.targets) {
|
||||
if (target.queryType === GrafanaQueryType.Annotations) {
|
||||
@ -90,7 +89,7 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
buffer.maxDelta = request.range.to.valueOf() - request.range.from.valueOf();
|
||||
}
|
||||
|
||||
queries.push(
|
||||
results.push(
|
||||
getGrafanaLiveSrv().getDataStream({
|
||||
key: `${request.requestId}.${counter++}`,
|
||||
addr: addr!,
|
||||
@ -99,15 +98,28 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
})
|
||||
);
|
||||
} else {
|
||||
queries.push(getRandomWalk(request));
|
||||
if (!target.queryType) {
|
||||
target.queryType = GrafanaQueryType.RandomWalk;
|
||||
}
|
||||
targets.push(target);
|
||||
}
|
||||
}
|
||||
// With a single query just return the results
|
||||
if (queries.length === 1) {
|
||||
return queries[0];
|
||||
|
||||
if (targets.length) {
|
||||
results.push(
|
||||
super.query({
|
||||
...request,
|
||||
targets,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (queries.length > 1) {
|
||||
return merge(...queries);
|
||||
|
||||
if (results.length) {
|
||||
// With a single query just return the results
|
||||
if (results.length === 1) {
|
||||
return results[0];
|
||||
}
|
||||
return merge(...results);
|
||||
}
|
||||
return of(); // nothing
|
||||
}
|
||||
@ -171,32 +183,3 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// Note that the query does not actually matter
|
||||
function getRandomWalk(request: DataQueryRequest): Observable<DataQueryResponse> {
|
||||
const { intervalMs, maxDataPoints, range, requestId } = request;
|
||||
|
||||
// Yes, this implementation ignores multiple targets! But that matches existing behavior
|
||||
const params: Record<string, any> = {
|
||||
intervalMs,
|
||||
maxDataPoints,
|
||||
from: range.from.valueOf(),
|
||||
to: range.to.valueOf(),
|
||||
};
|
||||
|
||||
return getBackendSrv()
|
||||
.fetch({
|
||||
url: '/api/tsdb/testdata/random-walk',
|
||||
method: 'GET',
|
||||
params,
|
||||
requestId,
|
||||
})
|
||||
.pipe(
|
||||
map((rsp: any) => {
|
||||
return toDataQueryResponse(rsp);
|
||||
}),
|
||||
catchError((err) => {
|
||||
return of(toDataQueryResponse(err));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -6,9 +6,13 @@ import { LiveDataFilter } from '@grafana/runtime';
|
||||
//----------------------------------------------
|
||||
|
||||
export enum GrafanaQueryType {
|
||||
RandomWalk = 'randomWalk',
|
||||
LiveMeasurements = 'measurements',
|
||||
Annotations = 'annotations',
|
||||
|
||||
// backend
|
||||
RandomWalk = 'randomWalk',
|
||||
List = 'list',
|
||||
Read = 'read',
|
||||
}
|
||||
|
||||
export interface GrafanaQuery extends DataQuery {
|
||||
@ -16,6 +20,7 @@ export interface GrafanaQuery extends DataQuery {
|
||||
channel?: string;
|
||||
filter?: LiveDataFilter;
|
||||
buffer?: number;
|
||||
path?: string; // for list and read
|
||||
}
|
||||
|
||||
export const defaultQuery: GrafanaQuery = {
|
||||
|
Loading…
Reference in New Issue
Block a user