From 5f1f820bb99eeca7a6dfe5f428b2b593d38585d4 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 10 Jun 2020 12:26:24 -0700 Subject: [PATCH] Influx: Support flux in the influx datasource (#25308) * add flux * add token to datasource config editor * add backend for flux * make the interpolated query available in query inspector * go mod tidy * Chore: fixes a couple of strict null errors in influxdb plugin Co-authored-by: kyle Co-authored-by: Lukas Siatka --- go.mod | 17 +- go.sum | 77 ++++++-- pkg/tsdb/influxdb/flux/builder.go | 182 ++++++++++++++++++ pkg/tsdb/influxdb/flux/builder_test.go | 46 +++++ pkg/tsdb/influxdb/flux/converters.go | 168 ++++++++++++++++ pkg/tsdb/influxdb/flux/executor.go | 88 +++++++++ pkg/tsdb/influxdb/flux/executor_test.go | 172 +++++++++++++++++ pkg/tsdb/influxdb/flux/flux.go | 102 ++++++++++ pkg/tsdb/influxdb/flux/macros.go | 45 +++++ pkg/tsdb/influxdb/flux/macros_test.go | 56 ++++++ pkg/tsdb/influxdb/flux/query_models.go | 88 +++++++++ pkg/tsdb/influxdb/flux/testdata/buckets.csv | 7 + pkg/tsdb/influxdb/flux/testdata/grouping.csv | 21 ++ pkg/tsdb/influxdb/flux/testdata/multiple.csv | 25 +++ pkg/tsdb/influxdb/flux/testdata/simple.csv | 6 + pkg/tsdb/influxdb/influxdb.go | 28 +++ .../influxdb/components/ConfigEditor.tsx | 72 +++++++ .../__snapshots__/ConfigEditor.test.tsx.snap | 108 +++++++++++ .../plugins/datasource/influxdb/datasource.ts | 105 +++++++--- .../datasource/influxdb/influx_query_model.ts | 2 +- .../influxdb/partials/query.editor.html | 36 +++- .../plugins/datasource/influxdb/query_ctrl.ts | 23 ++- .../influxdb/specs/datasource.test.ts | 14 +- .../app/plugins/datasource/influxdb/types.ts | 16 +- 24 files changed, 1449 insertions(+), 55 deletions(-) create mode 100644 pkg/tsdb/influxdb/flux/builder.go create mode 100644 pkg/tsdb/influxdb/flux/builder_test.go create mode 100644 pkg/tsdb/influxdb/flux/converters.go create mode 100644 pkg/tsdb/influxdb/flux/executor.go create mode 100644 pkg/tsdb/influxdb/flux/executor_test.go create mode 100644 pkg/tsdb/influxdb/flux/flux.go create mode 100644 pkg/tsdb/influxdb/flux/macros.go create mode 100644 pkg/tsdb/influxdb/flux/macros_test.go create mode 100644 pkg/tsdb/influxdb/flux/query_models.go create mode 100644 pkg/tsdb/influxdb/flux/testdata/buckets.csv create mode 100644 pkg/tsdb/influxdb/flux/testdata/grouping.csv create mode 100644 pkg/tsdb/influxdb/flux/testdata/multiple.csv create mode 100644 pkg/tsdb/influxdb/flux/testdata/simple.csv diff --git a/go.mod b/go.mod index 6a2c76eaef1..78e47ce2d83 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,8 @@ require ( github.com/go-sql-driver/mysql v1.5.0 github.com/go-stack/stack v1.8.0 github.com/gobwas/glob v0.2.3 - github.com/golang/protobuf v1.3.4 - github.com/google/go-cmp v0.3.1 + github.com/golang/protobuf v1.4.0 + github.com/google/go-cmp v0.4.0 github.com/gorilla/websocket v1.4.1 github.com/gosimple/slug v1.4.2 github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 @@ -35,6 +35,7 @@ require ( github.com/hashicorp/go-plugin v1.2.2 github.com/hashicorp/go-version v1.1.0 github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec + github.com/influxdata/influxdb-client-go v1.1.1-0.20200511153144-e63a28ffeba7 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af github.com/jung-kurt/gofpdf v1.10.1 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect @@ -70,13 +71,11 @@ require ( github.com/yudai/pp v2.0.1+incompatible // indirect go.uber.org/atomic v1.5.1 // indirect golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 - golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect - golang.org/x/net v0.0.0-20200202094626-16171245cfb2 - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e - golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 // indirect + golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 - google.golang.org/grpc v1.27.1 + google.golang.org/grpc v1.29.1 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/ini.v1 v1.46.0 @@ -85,7 +84,7 @@ require ( gopkg.in/mail.v2 v2.3.1 gopkg.in/redis.v5 v5.2.9 gopkg.in/square/go-jose.v2 v2.4.1 - gopkg.in/yaml.v2 v2.2.5 + gopkg.in/yaml.v2 v2.2.8 xorm.io/core v0.7.3 xorm.io/xorm v0.8.1 ) diff --git a/go.sum b/go.sum index 97c6964a4ef..35d101ccd22 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,7 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= @@ -47,10 +48,13 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/crewjam/saml v0.0.0-20191031171751-c42136edf9b1 h1:PKeiHI5SxrkdEtI8FVdk1ubBl2wjnOmHQf5D4ZJOKFE= github.com/crewjam/saml v0.0.0-20191031171751-c42136edf9b1/go.mod h1:pzACCdpqjQKTvpPZs5P3FzFNQ+RSOJX5StwHwh7ZUgw= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= +github.com/deepmap/oapi-codegen v1.3.6 h1:Wj44p9A0V0PJ+AUg0BWdyGcsS1LY18U+0rCuPQgK0+o= +github.com/deepmap/oapi-codegen v1.3.6/go.mod h1:aBozjEveG+33xPiP55Iw/XbVkhtZHEGLq3nxlX0+hfU= github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o= github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= @@ -59,7 +63,9 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= @@ -75,6 +81,9 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -108,17 +117,28 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v1.11.0 h1:O7CEyB8Cb3/DmtxODGtLHcEvpr81Jm5qLg/hsHnxA2A= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -156,6 +176,10 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec h1:CGkYB1Q7DSsH/ku+to+foV4agt2F2miquaLUgF6L178= github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/influxdata/influxdb-client-go v1.1.1-0.20200511153144-e63a28ffeba7 h1:4Inzo/mjTMm7DdqVNkbHmEo6UIwQDpWHMYdlu4+I/W4= +github.com/influxdata/influxdb-client-go v1.1.1-0.20200511153144-e63a28ffeba7/go.mod h1:ZVjaPW87aKp5hzyny2WVpWVF0UY+iqtPz9veOZ2T1zw= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= @@ -188,6 +212,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -197,10 +225,16 @@ github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbat github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0= github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE= github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattetti/filebuffer v1.0.0 h1:ixTvQ0JjBTwWbdpDZ98lLrydo7KRi8xNRIi5RFszsbY= github.com/mattetti/filebuffer v1.0.0/go.mod h1:X6nyAIge2JGVmuJt2MFCqmHrb/5IHiphfHtot0s5cnI= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= @@ -322,6 +356,11 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= +github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM= github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y= github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= @@ -345,8 +384,7 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y= golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -355,10 +393,8 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -375,12 +411,15 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -388,6 +427,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -395,15 +436,21 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -422,12 +469,9 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190805222050-c5a2fd39b72a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 h1:kJQZhwFzSwJS2BxboKjdZzWczQOZx8VuH7Y8hhuGUtM= -golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= @@ -447,8 +491,17 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= @@ -481,6 +534,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/tsdb/influxdb/flux/builder.go b/pkg/tsdb/influxdb/flux/builder.go new file mode 100644 index 00000000000..6fe03045e09 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/builder.go @@ -0,0 +1,182 @@ +package flux + +import ( + "fmt" + + "github.com/grafana/grafana-plugin-sdk-go/data" + influxdb2 "github.com/influxdata/influxdb-client-go" +) + +// Copied from: (Apache 2 license) +// https://github.com/influxdata/influxdb-client-go/blob/master/query.go#L30 +const ( + stringDatatype = "string" + doubleDatatype = "double" + boolDatatype = "bool" + longDatatype = "long" + uLongDatatype = "unsignedLong" + durationDatatype = "duration" + base64BinaryDataType = "base64Binary" + timeDatatypeRFC = "dateTime:RFC3339" + timeDatatypeRFCNano = "dateTime:RFC3339Nano" +) + +type columnInfo struct { + name string + converter *data.FieldConverter +} + +// This is an interface to help testing +type FrameBuilder struct { + tableId int64 + active *data.Frame + frames []*data.Frame + value *data.FieldConverter + columns []columnInfo + labels []string + maxPoints int // max points in a series + maxSeries int // max number of series + totalSeries int + isTimeSeries bool +} + +func isTag(schk string) bool { + return (schk != "result" && schk != "table" && schk[0] != '_') +} + +func getConverter(t string) (*data.FieldConverter, error) { + switch t { + case stringDatatype: + return &AnyToOptionalString, nil + case timeDatatypeRFC: + return &Int64ToOptionalInt64, nil + case timeDatatypeRFCNano: + return &Int64ToOptionalInt64, nil + case durationDatatype: + return &Int64ToOptionalInt64, nil + case doubleDatatype: + return &Float64ToOptionalFloat64, nil + case boolDatatype: + return &BoolToOptionalBool, nil + case longDatatype: + return &Int64ToOptionalInt64, nil + case uLongDatatype: + return &UInt64ToOptionalUInt64, nil + case base64BinaryDataType: + return &AnyToOptionalString, nil + } + + return nil, fmt.Errorf("No matching converter found for [%v]", t) +} + +// Init initializes the frame to be returned +// fields points at entries in the frame, and provides easier access +// names indexes the columns encountered +func (fb *FrameBuilder) Init(metadata *influxdb2.FluxTableMetadata) error { + columns := metadata.Columns() + fb.frames = make([]*data.Frame, 0) + fb.tableId = -1 + fb.value = nil + fb.columns = make([]columnInfo, 0) + fb.isTimeSeries = false + + for _, col := range columns { + switch { + case col.Name() == "_value": + if fb.value != nil { + return fmt.Errorf("multiple values found") + } + converter, err := getConverter(col.DataType()) + if err != nil { + return err + } + fb.value = converter + case col.Name() == "_measurement": + fb.isTimeSeries = true + case isTag(col.Name()): + fb.labels = append(fb.labels, col.Name()) + } + } + + if !fb.isTimeSeries { + fb.labels = make([]string, 0) + for _, col := range columns { + converter, err := getConverter(col.DataType()) + if err != nil { + return err + } + fb.columns = append(fb.columns, columnInfo{ + name: col.Name(), + converter: converter, + }) + } + } + + return nil +} + +// Append appends a single entry from an influxdb2 record to a data frame +// Values are appended to _value +// Tags are appended as labels +// _measurement holds the dataframe name +// _field holds the field name. +func (fb *FrameBuilder) Append(record *influxdb2.FluxRecord) error { + table, ok := record.ValueByKey("table").(int64) + if ok && table != fb.tableId { + fb.totalSeries++ + if fb.totalSeries > fb.maxSeries { + return fmt.Errorf("reached max series limit (%d)", fb.maxSeries) + } + + if fb.isTimeSeries { + // Series Data + labels := make(map[string]string) + for _, name := range fb.labels { + labels[name] = record.ValueByKey(name).(string) + } + fb.active = data.NewFrame( + record.Measurement(), + data.NewFieldFromFieldType(data.FieldTypeTime, 0), + data.NewFieldFromFieldType(fb.value.OutputFieldType, 0), + ) + + fb.active.Fields[0].Name = "Time" + fb.active.Fields[1].Name = record.Field() + fb.active.Fields[1].Labels = labels + } else { + fields := make([]*data.Field, len(fb.columns)) + for idx, col := range fb.columns { + fields[idx] = data.NewFieldFromFieldType(col.converter.OutputFieldType, 0) + fields[idx].Name = col.name + } + fb.active = data.NewFrame("", fields...) + } + + fb.frames = append(fb.frames, fb.active) + fb.tableId = table + } + + if fb.isTimeSeries { + val, err := fb.value.Converter(record.Value()) + if err != nil { + return err + } + fb.active.Fields[0].Append(record.Time()) + fb.active.Fields[1].Append(val) + } else { + // Table view + for idx, col := range fb.columns { + val, err := col.converter.Converter(record.ValueByKey(col.name)) + if err != nil { + return err + } + fb.active.Fields[idx].Append(val) + } + } + + if fb.active.Fields[0].Len() > fb.maxPoints { + return fmt.Errorf("returned too many points in a series: %d", fb.maxPoints) + } + + return nil +} diff --git a/pkg/tsdb/influxdb/flux/builder_test.go b/pkg/tsdb/influxdb/flux/builder_test.go new file mode 100644 index 00000000000..bea2e284652 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/builder_test.go @@ -0,0 +1,46 @@ +package flux + +import ( + "regexp" + "testing" +) + +var isField = regexp.MustCompile(`^_(time|value|measurement|field|start|stop)$`) + +func TestColumnIdentification(t *testing.T) { + t.Run("Test Field Identification", func(t *testing.T) { + fieldNames := []string{"_time", "_value", "_measurement", "_field", "_start", "_stop"} + for _, item := range fieldNames { + if !isField.MatchString(item) { + t.Fatal("Field", item, "Expected field, but got false") + } + } + }) + + t.Run("Test Not Field Identification", func(t *testing.T) { + fieldNames := []string{"_header", "_cpu", "_hello", "_doctor"} + for _, item := range fieldNames { + if isField.MatchString(item) { + t.Fatal("Field", item, "Expected NOT a field, but got true") + } + } + }) + + t.Run("Test Tag Identification", func(t *testing.T) { + tagNames := []string{"header", "value", "tag"} + for _, item := range tagNames { + if !isTag(item) { + t.Fatal("Tag", item, "Expected tag, but got false") + } + } + }) + + t.Run("Test Special Case Tag Identification", func(t *testing.T) { + notTagNames := []string{"table", "result"} + for _, item := range notTagNames { + if isTag(item) { + t.Fatal("Special tag", item, "Expected NOT a tag, but got true") + } + } + }) +} diff --git a/pkg/tsdb/influxdb/flux/converters.go b/pkg/tsdb/influxdb/flux/converters.go new file mode 100644 index 00000000000..eceb2e26dd1 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/converters.go @@ -0,0 +1,168 @@ +package flux + +import ( + "fmt" + "strconv" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// Int64NOOP ..... +var Int64NOOP = data.FieldConverter{ + OutputFieldType: data.FieldTypeInt64, +} + +// BoolNOOP ..... +var BoolNOOP = data.FieldConverter{ + OutputFieldType: data.FieldTypeBool, +} + +// Float64NOOP ..... +var Float64NOOP = data.FieldConverter{ + OutputFieldType: data.FieldTypeFloat64, +} + +// StringNOOP value is already in the proper format +var StringNOOP = data.FieldConverter{ + OutputFieldType: data.FieldTypeString, +} + +// AnyToOptionalString any value as a string +var AnyToOptionalString = data.FieldConverter{ + OutputFieldType: data.FieldTypeNullableString, + Converter: func(v interface{}) (interface{}, error) { + if v == nil { + return nil, nil + } + str := fmt.Sprintf("%+v", v) // the +v adds field names + return &str, nil + }, +} + +// Float64ToOptionalFloat64 optional float value +var Float64ToOptionalFloat64 = data.FieldConverter{ + OutputFieldType: data.FieldTypeNullableFloat64, + Converter: func(v interface{}) (interface{}, error) { + if v == nil { + return nil, nil + } + val, ok := v.(float64) + if !ok { // or return some default value instead of erroring + return nil, fmt.Errorf("[float] expected float64 input but got type %T", v) + } + return &val, nil + }, +} + +// Int64ToOptionalInt64 optional int value +var Int64ToOptionalInt64 = data.FieldConverter{ + OutputFieldType: data.FieldTypeNullableInt64, + Converter: func(v interface{}) (interface{}, error) { + if v == nil { + return nil, nil + } + val, ok := v.(int64) + if !ok { // or return some default value instead of erroring + return nil, fmt.Errorf("[int] expected int64 input but got type %T", v) + } + return &val, nil + }, +} + +// UInt64ToOptionalUInt64 optional int value +var UInt64ToOptionalUInt64 = data.FieldConverter{ + OutputFieldType: data.FieldTypeNullableUint64, + Converter: func(v interface{}) (interface{}, error) { + if v == nil { + return nil, nil + } + val, ok := v.(uint64) + if !ok { // or return some default value instead of erroring + return nil, fmt.Errorf("[uint] expected uint64 input but got type %T", v) + } + return &val, nil + }, +} + +// BoolToOptionalBool optional int value +var BoolToOptionalBool = data.FieldConverter{ + OutputFieldType: data.FieldTypeNullableBool, + Converter: func(v interface{}) (interface{}, error) { + if v == nil { + return nil, nil + } + val, ok := v.(bool) + if !ok { // or return some default value instead of erroring + return nil, fmt.Errorf("[bool] expected bool input but got type %T", v) + } + return &val, nil + }, +} + +// RFC3339StringToNullableTime ..... +func RFC3339StringToNullableTime(s string) (*time.Time, error) { + if s == "" { + return nil, nil + } + + rv, err := time.Parse(time.RFC3339, s) + if err != nil { + return nil, err + } + + u := rv.UTC() + return &u, nil +} + +// StringToOptionalFloat64 string to float +var StringToOptionalFloat64 = data.FieldConverter{ + OutputFieldType: data.FieldTypeNullableFloat64, + Converter: func(v interface{}) (interface{}, error) { + if v == nil { + return nil, nil + } + val, ok := v.(string) + if !ok { // or return some default value instead of erroring + return nil, fmt.Errorf("[floatz] expected string input but got type %T", v) + } + fV, err := strconv.ParseFloat(val, 64) + return &fV, err + }, +} + +// Float64EpochSecondsToTime numeric seconds to time +var Float64EpochSecondsToTime = data.FieldConverter{ + OutputFieldType: data.FieldTypeTime, + Converter: func(v interface{}) (interface{}, error) { + fV, ok := v.(float64) + if !ok { // or return some default value instead of erroring + return nil, fmt.Errorf("[seconds] expected float64 input but got type %T", v) + } + return time.Unix(int64(fV), 0).UTC(), nil + }, +} + +// Float64EpochMillisToTime convert to time +var Float64EpochMillisToTime = data.FieldConverter{ + OutputFieldType: data.FieldTypeTime, + Converter: func(v interface{}) (interface{}, error) { + fV, ok := v.(float64) + if !ok { // or return some default value instead of erroring + return nil, fmt.Errorf("[ms] expected float64 input but got type %T", v) + } + return time.Unix(0, int64(fV)*int64(time.Millisecond)).UTC(), nil + }, +} + +// Boolean ... +var Boolean = data.FieldConverter{ + OutputFieldType: data.FieldTypeBool, + Converter: func(v interface{}) (interface{}, error) { + fV, ok := v.(bool) + if !ok { // or return some default value instead of erroring + return nil, fmt.Errorf("[ms] expected bool input but got type %T", v) + } + return fV, nil + }, +} diff --git a/pkg/tsdb/influxdb/flux/executor.go b/pkg/tsdb/influxdb/flux/executor.go new file mode 100644 index 00000000000..3ad252a2479 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/executor.go @@ -0,0 +1,88 @@ +package flux + +import ( + "context" + "fmt" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + influxdb2 "github.com/influxdata/influxdb-client-go" +) + +// ExecuteQuery runs a flux query using the QueryModel to interpolate the query and the runner to execute it. +// maxSeries somehow limits the response. +func ExecuteQuery(ctx context.Context, query QueryModel, runner queryRunner, maxSeries int) (dr backend.DataResponse) { + dr = backend.DataResponse{} + + flux, err := Interpolate(query) + if err != nil { + dr.Error = err + return + } + + glog.Debug("Flux", "interpolated query", flux) + + tables, err := runner.runQuery(ctx, flux) + if err != nil { + dr.Error = err + return + } + + dr = readDataFrames(tables, int(float64(query.MaxDataPoints)*1.5), maxSeries) + + for _, frame := range dr.Frames { + if frame.Meta == nil { + frame.Meta = &data.FrameMeta{} + } + frame.Meta.ExecutedQueryString = flux + } + + return dr +} + +func readDataFrames(result *influxdb2.QueryTableResult, maxPoints int, maxSeries int) (dr backend.DataResponse) { + dr = backend.DataResponse{} + + builder := &FrameBuilder{ + maxPoints: maxPoints, + maxSeries: maxSeries, + } + + for result.Next() { + // Observe when there is new grouping key producing new table + if result.TableChanged() { + if builder.frames != nil { + for _, frame := range builder.frames { + dr.Frames = append(dr.Frames, frame) + } + } + err := builder.Init(result.TableMetadata()) + if err != nil { + dr.Error = err + return + } + } + + if builder.frames == nil { + dr.Error = fmt.Errorf("Invalid state") + return dr + } + + err := builder.Append(result.Record()) + if err != nil { + dr.Error = err + break + } + } + + // Add the inprogress record + if builder.frames != nil { + for _, frame := range builder.frames { + dr.Frames = append(dr.Frames, frame) + } + } + + // Attach any errors (may be null) + dr.Error = result.Err() + return dr +} diff --git a/pkg/tsdb/influxdb/flux/executor_test.go b/pkg/tsdb/influxdb/flux/executor_test.go new file mode 100644 index 00000000000..e7209826950 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/executor_test.go @@ -0,0 +1,172 @@ +package flux + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + influxdb2 "github.com/influxdata/influxdb-client-go" +) + +//-------------------------------------------------------------- +// TestData -- reads result from saved files +//-------------------------------------------------------------- + +// MockRunner reads local file path for testdata. +type MockRunner struct { + testDataPath string +} + +func (r *MockRunner) runQuery(ctx context.Context, q string) (*influxdb2.QueryTableResult, error) { + bytes, err := ioutil.ReadFile("./testdata/" + r.testDataPath) + if err != nil { + return nil, err + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(100 * time.Millisecond) + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(bytes) + } else { + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + client := influxdb2.NewClient(server.URL, "a") + return client.QueryApi("x").Query(ctx, q) +} + +func TestExecuteSimple(t *testing.T) { + ctx := context.Background() + + t.Run("Simple Test", func(t *testing.T) { + runner := &MockRunner{ + testDataPath: "simple.csv", + } + + dr := ExecuteQuery(ctx, QueryModel{MaxDataPoints: 100}, runner, 50) + + if dr.Error != nil { + t.Fatal(dr.Error) + } + + if len(dr.Frames) != 1 { + t.Fatalf("Expected 1 frame, received [%d] frames", len(dr.Frames)) + } + + if !strings.Contains(dr.Frames[0].Name, "test") { + t.Fatalf("Frame must match _measurement column. Expected [%s] Got [%s]", "test", dr.Frames[0].Name) + } + + if len(dr.Frames[0].Fields[1].Labels) != 2 { + t.Fatalf("Error parsing labels. Expected [%d] Got [%d]", 2, len(dr.Frames[0].Fields[1].Labels)) + } + + if dr.Frames[0].Fields[0].Name != "Time" { + t.Fatalf("Error parsing fields. Field 1 should always be time. Got name [%s]", dr.Frames[0].Fields[0].Name) + } + + st, _ := dr.Frames[0].StringTable(-1, -1) + fmt.Println(st) + fmt.Println("----------------------") + }) +} + +func TestExecuteMultiple(t *testing.T) { + ctx := context.Background() + + t.Run("Multiple Test", func(t *testing.T) { + runner := &MockRunner{ + testDataPath: "multiple.csv", + } + + dr := ExecuteQuery(ctx, QueryModel{MaxDataPoints: 100}, runner, 50) + + if dr.Error != nil { + t.Fatal(dr.Error) + } + + if len(dr.Frames) != 4 { + t.Fatalf("Expected 4 frames, received [%d] frames", len(dr.Frames)) + } + + if !strings.Contains(dr.Frames[0].Name, "test") { + t.Fatalf("Frame must include _measurement column. Expected [%s] Got [%s]", "test", dr.Frames[0].Name) + } + + if len(dr.Frames[0].Fields[1].Labels) != 2 { + t.Fatalf("Error parsing labels. Expected [%d] Got [%d]", 2, len(dr.Frames[0].Fields[1].Labels)) + } + + if dr.Frames[0].Fields[0].Name != "Time" { + t.Fatalf("Error parsing fields. Field 1 should always be time. Got name [%s]", dr.Frames[0].Fields[0].Name) + } + + st, _ := dr.Frames[0].StringTable(-1, -1) + fmt.Println(st) + fmt.Println("----------------------") + }) +} + +func TestExecuteGrouping(t *testing.T) { + ctx := context.Background() + + t.Run("Grouping Test", func(t *testing.T) { + runner := &MockRunner{ + testDataPath: "grouping.csv", + } + + dr := ExecuteQuery(ctx, QueryModel{MaxDataPoints: 100}, runner, 50) + + if dr.Error != nil { + t.Fatal(dr.Error) + } + + if len(dr.Frames) != 3 { + t.Fatalf("Expected 3 frames, received [%d] frames", len(dr.Frames)) + } + + if !strings.Contains(dr.Frames[0].Name, "system") { + t.Fatalf("Frame must match _measurement column. Expected [%s] Got [%s]", "test", dr.Frames[0].Name) + } + + if len(dr.Frames[0].Fields[1].Labels) != 1 { + t.Fatalf("Error parsing labels. Expected [%d] Got [%d]", 1, len(dr.Frames[0].Fields[1].Labels)) + } + + if dr.Frames[0].Fields[0].Name != "Time" { + t.Fatalf("Error parsing fields. Field 1 should always be time. Got name [%s]", dr.Frames[0].Fields[0].Name) + } + + st, _ := dr.Frames[0].StringTable(-1, -1) + fmt.Println(st) + fmt.Println("----------------------") + }) +} + +func TestBuckets(t *testing.T) { + ctx := context.Background() + + t.Run("Buckes", func(t *testing.T) { + runner := &MockRunner{ + testDataPath: "buckets.csv", + } + + dr := ExecuteQuery(ctx, QueryModel{MaxDataPoints: 100}, runner, 50) + + if dr.Error != nil { + t.Fatal(dr.Error) + } + + st, _ := dr.Frames[0].StringTable(-1, -1) + fmt.Println(st) + fmt.Println("----------------------") + }) +} diff --git a/pkg/tsdb/influxdb/flux/flux.go b/pkg/tsdb/influxdb/flux/flux.go new file mode 100644 index 00000000000..abdaafc7bbd --- /dev/null +++ b/pkg/tsdb/influxdb/flux/flux.go @@ -0,0 +1,102 @@ +package flux + +import ( + "context" + "fmt" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/tsdb" + influxdb2 "github.com/influxdata/influxdb-client-go" +) + +var ( + glog log.Logger +) + +func init() { + glog = log.New("tsdb.influx_flux") +} + +// Query builds flux queries, executes them, and returns the results. +func Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { + tRes := &tsdb.Response{ + Results: make(map[string]*tsdb.QueryResult), + } + runner, err := RunnerFromDataSource(dsInfo) + if err != nil { + return nil, err + } + + for _, query := range tsdbQuery.Queries { + + qm, err := GetQueryModelTSDB(query, tsdbQuery.TimeRange, dsInfo) + if err != nil { + tRes.Results[query.RefId] = &tsdb.QueryResult{Error: err} + continue + } + + res := ExecuteQuery(context.Background(), *qm, runner, 10) + + tRes.Results[query.RefId] = backendDataResponseToTSDBResponse(&res, query.RefId) + } + return tRes, nil +} + +// Runner is an influxdb2 Client with an attached org property and is used +// for running flux queries. +type Runner struct { + client influxdb2.Client + org string +} + +// This is an interface to help testing +type queryRunner interface { + runQuery(ctx context.Context, q string) (*influxdb2.QueryTableResult, error) +} + +// runQuery executes fluxQuery against the Runner's organization and returns an flux typed result. +func (r *Runner) runQuery(ctx context.Context, fluxQuery string) (*influxdb2.QueryTableResult, error) { + return r.client.QueryApi(r.org).Query(ctx, fluxQuery) +} + +// RunnerFromDataSource creates a runner from the datasource model (the datasource instance's configuration). +func RunnerFromDataSource(dsInfo *models.DataSource) (*Runner, error) { + org := dsInfo.JsonData.Get("organization").MustString("") + if org == "" { + return nil, fmt.Errorf("missing organization in datasource configuration") + } + + url := dsInfo.Url + if url == "" { + return nil, fmt.Errorf("missing url from datasource configuration") + } + token, found := dsInfo.SecureJsonData.DecryptedValue("token") + if !found { + return nil, fmt.Errorf("token is missing from datasource configuration and is needed to use Flux") + } + + return &Runner{ + client: influxdb2.NewClient(url, token), + org: org, + }, nil + +} + +// backendDataResponseToTSDBResponse takes the SDK's style response and changes it into a +// tsdb.QueryResult. This is a wrapper so less of existing code needs to be changed. This should +// be able to be removed in the near future https://github.com/grafana/grafana/pull/25472. +func backendDataResponseToTSDBResponse(dr *backend.DataResponse, refID string) *tsdb.QueryResult { + qr := &tsdb.QueryResult{RefId: refID} + + if dr.Error != nil { + qr.Error = dr.Error + return qr + } + + if dr.Frames != nil { + qr.Dataframes = tsdb.NewDecodedDataFrames(dr.Frames) + } + return qr +} diff --git a/pkg/tsdb/influxdb/flux/macros.go b/pkg/tsdb/influxdb/flux/macros.go new file mode 100644 index 00000000000..f15f6324eeb --- /dev/null +++ b/pkg/tsdb/influxdb/flux/macros.go @@ -0,0 +1,45 @@ +package flux + +import ( + "fmt" + "regexp" + "strings" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" +) + +const variableFilter = `(?m)([a-zA-Z]+)\.([a-zA-Z]+)` + +// Interpolate processes macros +func Interpolate(query QueryModel) (string, error) { + + flux := query.RawQuery + + variableFilterExp, err := regexp.Compile(variableFilter) + matches := variableFilterExp.FindAllStringSubmatch(flux, -1) + if matches != nil { + timeRange := query.TimeRange + from := timeRange.From.UTC().Format(time.RFC3339) + to := timeRange.To.UTC().Format(time.RFC3339) + for _, match := range matches { + switch match[2] { + case "timeRangeStart": + flux = strings.ReplaceAll(flux, match[0], from) + case "timeRangeStop": + flux = strings.ReplaceAll(flux, match[0], to) + case "windowPeriod": + flux = strings.ReplaceAll(flux, match[0], query.Interval.String()) + case "bucket": + flux = strings.ReplaceAll(flux, match[0], "\""+query.Options.Bucket+"\"") + case "defaultBucket": + flux = strings.ReplaceAll(flux, match[0], "\""+query.Options.DefaultBucket+"\"") + case "organization": + flux = strings.ReplaceAll(flux, match[0], "\""+query.Options.Organization+"\"") + } + } + } + + backend.Logger.Info(fmt.Sprintf("%s => %v", flux, query.Options)) + return flux, err +} diff --git a/pkg/tsdb/influxdb/flux/macros_test.go b/pkg/tsdb/influxdb/flux/macros_test.go new file mode 100644 index 00000000000..3a939bbc827 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/macros_test.go @@ -0,0 +1,56 @@ +package flux + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/grafana/grafana-plugin-sdk-go/backend" +) + +func TestInterpolate(t *testing.T) { + // Unix sec: 1500376552 + // Unix ms: 1500376552001 + + timeRange := backend.TimeRange{ + From: time.Unix(0, 0), + To: time.Unix(0, 0), + } + + options := QueryOptions{ + Organization: "grafana1", + Bucket: "grafana2", + DefaultBucket: "grafana3", + } + + tests := []struct { + name string + before string + after string + }{ + { + name: "interpolate flux variables", + before: `v.timeRangeStart, something.timeRangeStop, XYZ.bucket, uuUUu.defaultBucket, aBcDefG.organization, window.windowPeriod, a91{}.bucket`, + after: `1970-01-01T00:00:00Z, 1970-01-01T00:00:00Z, "grafana2", "grafana3", "grafana1", 1s, a91{}.bucket`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + query := QueryModel{ + RawQuery: tt.before, + Options: options, + TimeRange: timeRange, + MaxDataPoints: 1, + Interval: 1000 * 1000 * 1000, + } + interpolatedQuery, err := Interpolate(query) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tt.after, interpolatedQuery); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/tsdb/influxdb/flux/query_models.go b/pkg/tsdb/influxdb/flux/query_models.go new file mode 100644 index 00000000000..be93fac085c --- /dev/null +++ b/pkg/tsdb/influxdb/flux/query_models.go @@ -0,0 +1,88 @@ +package flux + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/tsdb" +) + +// QueryOptions represents datasource configuration options +type QueryOptions struct { + Bucket string `json:"bucket"` + DefaultBucket string `json:"defaultBucket"` + Organization string `json:"organization"` +} + +// QueryModel represents a spreadsheet query. +type QueryModel struct { + RawQuery string `json:"query"` + Options QueryOptions `json:"options"` + + // Not from JSON + TimeRange backend.TimeRange `json:"-"` + MaxDataPoints int64 `json:"-"` + Interval time.Duration `json:"-"` +} + +// The following is commented out but kept as it should be useful when +// restoring this code to be closer to the SDK's models. + +// func GetQueryModel(query backend.DataQuery) (*QueryModel, error) { +// model := &QueryModel{} + +// err := json.Unmarshal(query.JSON, &model) +// if err != nil { +// return nil, fmt.Errorf("error reading query: %s", err.Error()) +// } + +// // Copy directly from the well typed query +// model.TimeRange = query.TimeRange +// model.MaxDataPoints = query.MaxDataPoints +// model.Interval = query.Interval +// return model, nil +// } + +// GetQueryModelTSDB builds a QueryModel from tsdb.Query information and datasource configuration (dsInfo). +func GetQueryModelTSDB(query *tsdb.Query, timeRange *tsdb.TimeRange, dsInfo *models.DataSource) (*QueryModel, error) { + model := &QueryModel{} + queryBytes, err := query.Model.Encode() + if err != nil { + return nil, fmt.Errorf("failed to re-encode the flux query into JSON: %w", err) + } + + err = json.Unmarshal(queryBytes, &model) + if err != nil { + return nil, fmt.Errorf("error reading query: %s", err.Error()) + } + if model.Options.DefaultBucket == "" { + model.Options.DefaultBucket = dsInfo.JsonData.Get("defaultBucket").MustString("") + } + if model.Options.Bucket == "" { + model.Options.Bucket = model.Options.DefaultBucket + } + if model.Options.Organization == "" { + model.Options.Organization = dsInfo.JsonData.Get("organization").MustString("") + } + startTime, err := timeRange.ParseFrom() + if err != nil { + return nil, err + } + + endTime, err := timeRange.ParseTo() + if err != nil { + return nil, err + } + + // Copy directly from the well typed query + model.TimeRange = backend.TimeRange{ + From: startTime, + To: endTime, + } + model.MaxDataPoints = query.MaxDataPoints + model.Interval = time.Millisecond * time.Duration(query.IntervalMs) + return model, nil +} diff --git a/pkg/tsdb/influxdb/flux/testdata/buckets.csv b/pkg/tsdb/influxdb/flux/testdata/buckets.csv new file mode 100644 index 00000000000..fa7beaa457f --- /dev/null +++ b/pkg/tsdb/influxdb/flux/testdata/buckets.csv @@ -0,0 +1,7 @@ +#datatype,string,long,string,string,string,string,long +#group,false,false,false,false,true,false,false +#default,_result,,,,,, +,result,table,name,id,organizationID,retentionPolicy,retentionPeriod +,,0,grafana,059b46a59abab001,059b46a59abab000,,604800000000000 +,,0,_tasks,059b46a59abab002,059b46a59abab000,,259200000000000 +,,0,_monitoring,059b46a59abab003,059b46a59abab000,,604800000000000 diff --git a/pkg/tsdb/influxdb/flux/testdata/grouping.csv b/pkg/tsdb/influxdb/flux/testdata/grouping.csv new file mode 100644 index 00000000000..f53a8ad24b0 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/testdata/grouping.csv @@ -0,0 +1,21 @@ +#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,string,string,string,double,dateTime:RFC3339 +#group,false,false,true,true,true,true,true,false,false +#default,mean,,,,,,,, +,result,table,_start,_stop,_field,_measurement,host,_value,_time +,,0,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load1,system,hostname,,2020-05-05T18:38:50Z +,,0,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load1,system,hostname,3.56,2020-05-05T18:39:00Z +,,0,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load1,system,hostname,,2020-05-05T19:38:47.207881833Z +,,1,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load15,system,hostname,,2020-05-05T18:38:50Z +,,1,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load15,system,hostname,2.51,2020-05-05T18:39:00Z +,,1,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load15,system,hostname,1.74,2020-05-05T19:38:40Z +,,1,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load15,system,hostname,,2020-05-05T19:38:47.207881833Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,,2020-05-05T18:38:50Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,3.14,2020-05-05T18:39:00Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,3.04,2020-05-05T18:39:10Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.8,2020-05-05T19:37:50Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.76,2020-05-05T19:38:00Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.75,2020-05-05T19:38:10Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.71,2020-05-05T19:38:20Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.77,2020-05-05T19:38:30Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.71,2020-05-05T19:38:40Z +,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,,2020-05-05T19:38:47.207881833Z \ No newline at end of file diff --git a/pkg/tsdb/influxdb/flux/testdata/multiple.csv b/pkg/tsdb/influxdb/flux/testdata/multiple.csv new file mode 100644 index 00000000000..9bd45c4dbd9 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/testdata/multiple.csv @@ -0,0 +1,25 @@ +#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string +#group,false,false,true,true,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,a,b +,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,1.4,f,test,1,adsfasdf +,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,1,adsfasdf +#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,string,string,string +#group,false,false,true,true,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,a,b +,,1,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,4,i,test,1,adsfasdf +,,1,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,-1,i,test,1,adsfasdf +#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,bool,string,string,string,string +#group,false,false,true,true,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,a,b +,,2,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.62797864Z,false,f,test,0,adsfasdf +,,2,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.969100374Z,true,f,test,0,adsfasdf +#datatype,string,long,dateTime:RFC3339Nano,dateTime:RFC3339Nano,dateTime:RFC3339Nano,unsignedLong,string,string,string,string +#group,false,false,true,true,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,a,b +,,3,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.62797864Z,0,i,test,0,adsfasdf +,,3,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.969100374Z,2,i,test,0,adsfasdf + diff --git a/pkg/tsdb/influxdb/flux/testdata/simple.csv b/pkg/tsdb/influxdb/flux/testdata/simple.csv new file mode 100644 index 00000000000..1f9acceda3d --- /dev/null +++ b/pkg/tsdb/influxdb/flux/testdata/simple.csv @@ -0,0 +1,6 @@ +#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string +#group,false,false,true,true,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,a,b +,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,1.4,f,test,1,adsfasdf +,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,1,adsfasdf diff --git a/pkg/tsdb/influxdb/influxdb.go b/pkg/tsdb/influxdb/influxdb.go index 3b8f365211a..9d8e98bb659 100644 --- a/pkg/tsdb/influxdb/influxdb.go +++ b/pkg/tsdb/influxdb/influxdb.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb" + "github.com/grafana/grafana/pkg/tsdb/influxdb/flux" "golang.org/x/net/context/ctxhttp" ) @@ -42,8 +43,35 @@ func init() { tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxDBExecutor) } +func AllFlux(queries *tsdb.TsdbQuery) (bool, error) { + var hasFlux bool + var allFlux bool + for i, q := range queries.Queries { + qType := q.Model.Get("queryType").MustString("") + if qType == "Flux" { + hasFlux = true + if i == 0 && hasFlux { + allFlux = true + continue + } + } + if allFlux && qType != "Flux" { + return true, fmt.Errorf("when using flux, all queries must be a flux query") + } + } + return allFlux, nil +} + func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { result := &tsdb.Response{} + allFlux, err := AllFlux(tsdbQuery) + if err != nil { + return nil, err + } + + if allFlux { + return flux.Query(ctx, dsInfo, tsdbQuery) + } query, err := e.getQuery(dsInfo, tsdbQuery.Queries, tsdbQuery) if err != nil { diff --git a/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx b/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx index f0cd23881cb..bea7f7b0292 100644 --- a/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx @@ -24,6 +24,21 @@ export class ConfigEditor extends PureComponent { updateDatasourcePluginResetOption(this.props, 'password'); }; + onResetToken = () => { + updateDatasourcePluginResetOption(this.props, 'token'); + }; + + onToggleFlux = (event: React.SyntheticEvent) => { + const { options, onOptionsChange } = this.props; + onOptionsChange({ + ...options, + jsonData: { + ...options.jsonData, + enableFlux: !options.jsonData.enableFlux, + }, + }); + }; + render() { const { options, onOptionsChange } = this.props; const { secureJsonFields } = options; @@ -39,6 +54,50 @@ export class ConfigEditor extends PureComponent {

InfluxDB Details

+
+ +
+ + {options.jsonData.enableFlux && ( + <> +
+
+ Organization +
+ +
+
+
+
+
+ Default Bucket +
+ +
+
+
+ +
+
+ + )} +
Database @@ -76,6 +135,19 @@ export class ConfigEditor extends PureComponent { />
+
+
+ +
+
+
+ +
@@ -101,6 +112,22 @@ exports[`Render should disable basic auth password input 1`] = ` />
+
+
+ +
+
@@ -258,6 +285,17 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
+
+ +
@@ -318,6 +356,22 @@ exports[`Render should hide basic auth fields when switch off 1`] = ` />
+
+
+ +
+
@@ -475,6 +529,17 @@ exports[`Render should hide white listed cookies input when browser access chose
+
+ +
@@ -535,6 +600,22 @@ exports[`Render should hide white listed cookies input when browser access chose />
+
+
+ +
+
@@ -692,6 +773,17 @@ exports[`Render should render component 1`] = `
+
+ +
@@ -752,6 +844,22 @@ exports[`Render should render component 1`] = ` />
+
+
+ +
+
diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 4c34dc53f53..bafbefd99fc 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -1,17 +1,17 @@ import _ from 'lodash'; -import { dateMath, DataSourceApi, DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; +import { dateMath, DataSourceInstanceSettings, ScopedVars, DataQueryRequest, DataQueryResponse } from '@grafana/data'; import InfluxSeries from './influx_series'; import InfluxQueryModel from './influx_query_model'; import ResponseParser from './response_parser'; import { InfluxQueryBuilder } from './query_builder'; -import { InfluxQuery, InfluxOptions } from './types'; -import { getBackendSrv } from '@grafana/runtime'; -import { TemplateSrv } from 'app/features/templating/template_srv'; +import { InfluxQuery, InfluxOptions, InfluxQueryType } from './types'; +import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime'; +import { Observable, from } from 'rxjs'; -export default class InfluxDatasource extends DataSourceApi { +export default class InfluxDatasource extends DataSourceWithBackend { type: string; - urls: any; + urls: string[]; username: string; password: string; name: string; @@ -21,17 +21,17 @@ export default class InfluxDatasource extends DataSourceApi, private templateSrv: TemplateSrv) { + constructor(instanceSettings: DataSourceInstanceSettings) { super(instanceSettings); this.type = 'influxdb'; this.urls = _.map(instanceSettings.url.split(','), url => { return url.trim(); }); - this.username = instanceSettings.username; - this.password = instanceSettings.password; + this.username = instanceSettings.username ?? ''; + this.password = instanceSettings.password ?? ''; this.name = instanceSettings.name; this.database = instanceSettings.database; this.basicAuth = instanceSettings.basicAuth; @@ -40,15 +40,69 @@ export default class InfluxDatasource extends DataSourceApi): Observable { + let hasFlux = false; + let allFlux = true; + + // Update the queryType fields and manage migrations + for (const target of request.targets) { + if (target.queryType === InfluxQueryType.Flux) { + hasFlux = true; + } else { + allFlux = false; + if (target.queryType === InfluxQueryType.Classic) { + delete target.rawQuery; + } else if (target.rawQuery) { + target.queryType = InfluxQueryType.InfluxQL; + } else if (target.queryType === InfluxQueryType.InfluxQL) { + target.rawQuery = true; // so the old version works + } else { + target.queryType = InfluxQueryType.Classic; // Explicitly set it to classic + delete target.rawQuery; + } + } + } + + // Proces flux queries (data frame request) + if (hasFlux) { + if (!this.enableFlux) { + throw 'Flux not enabled for this datasource'; + } + if (!allFlux) { + throw 'All queries must be flux'; + } + // Calls /api/tsdb/query + return super.query(request); + } + + // Fallback to classic query support + return from(this.classicQuery(request)); + } + + /** + * Only applied on flux queries + */ + applyTemplateVariables(query: InfluxQuery, scopedVars: ScopedVars): Record { + return { + ...query, + query: getTemplateSrv().replace(query.query, scopedVars), // The raw query text + }; + } + + /** + * The unchanged pre 7.1 query implementation + */ + async classicQuery(options: any): Promise { let timeFilter = this.getTimeFilter(options); const scopedVars = options.scopedVars; const targets = _.cloneDeep(options.targets); const queryTargets: any[] = []; let queryModel: InfluxQueryModel; let i, y; + const templateSrv = getTemplateSrv(); let allQueries = _.map(targets, target => { if (target.hide) { @@ -60,7 +114,7 @@ export default class InfluxDatasource extends DataSourceApi { if (current !== '') { @@ -74,7 +128,7 @@ export default class InfluxDatasource extends DataSourceApi 0) { timeFilter += ' AND ' + queryModel.renderAdhocFilters(adhocFilters); } @@ -83,7 +137,7 @@ export default class InfluxDatasource extends DataSourceApi { if (!data || !data.results) { @@ -100,7 +154,7 @@ export default class InfluxDatasource extends DataSourceApi { if (!data || !data.results || !data.results[0]) { @@ -150,16 +204,18 @@ export default class InfluxDatasource extends DataSourceApi 0) { + const templateSrv = getTemplateSrv(); + expandedQueries = queries.map(query => { const expandedQuery = { ...query, datasource: this.name, - measurement: this.templateSrv.replace(query.measurement, scopedVars, 'regex'), - policy: this.templateSrv.replace(query.policy, scopedVars, 'regex'), + measurement: templateSrv.replace(query.measurement ?? '', scopedVars, 'regex'), + policy: templateSrv.replace(query.policy ?? '', scopedVars, 'regex'), }; if (query.rawQuery) { - expandedQuery.query = this.templateSrv.replace(query.query, scopedVars, 'regex'); + expandedQuery.query = templateSrv.replace(query.query ?? '', scopedVars, 'regex'); } if (query.tags) { const expandedTags = query.tags.map(tag => { const expandedTag = { ...tag, - value: this.templateSrv.replace(tag.value, null, 'regex'), + value: templateSrv.replace(tag.value, null, 'regex'), }; return expandedTag; }); @@ -203,7 +261,7 @@ export default class InfluxDatasource extends DataSourceApi { return this.responseParser.parse(query, resp); @@ -253,6 +311,7 @@ export default class InfluxDatasource extends DataSourceApi -
+
+
+ +
+ +
+
+
+
+
+
+ + +
+
+ +
+
+ +