Merge branch 'dneth-multidatasource-pr' into backend

This commit is contained in:
Alexander Zobnin
2019-12-13 14:28:44 +03:00
22 changed files with 6337 additions and 781 deletions

View File

@@ -1,7 +0,0 @@
{
"presets": [
"env",
"react"
],
"retainLines": true
}

7
babel.config.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
"presets": [
[ "@babel/env", { targets: { node: 'current' } } ],
"@babel/react"
],
"retainLines": true
}

8
go.mod
View File

@@ -5,10 +5,16 @@ go 1.12
require ( require (
github.com/bitly/go-simplejson v0.5.0 github.com/bitly/go-simplejson v0.5.0
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.3.1 // indirect
github.com/grafana/grafana_plugin_model v0.0.0-20180518082423-84176c64269d github.com/grafana/grafana_plugin_model v0.0.0-20180518082423-84176c64269d
github.com/hashicorp/go-hclog v0.9.2 github.com/hashicorp/go-hclog v0.9.2
github.com/hashicorp/go-plugin v1.0.1 github.com/hashicorp/go-plugin v1.0.1
github.com/kr/pretty v0.1.0 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
golang.org/x/net v0.0.0-20190311183353-d8887717615a github.com/pkg/errors v0.8.1 // indirect
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
golang.org/x/text v0.3.2 // indirect
gotest.tools v2.2.0+incompatible
) )

17
go.sum
View File

@@ -9,6 +9,10 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/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/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/grafana/grafana_plugin_model v0.0.0-20180518082423-84176c64269d h1:Ep6bjWDwurT9NpUATiqa8NYIllmZbLDXLn6Ib4lCtAA= github.com/grafana/grafana_plugin_model v0.0.0-20180518082423-84176c64269d h1:Ep6bjWDwurT9NpUATiqa8NYIllmZbLDXLn6Ib4lCtAA=
github.com/grafana/grafana_plugin_model v0.0.0-20180518082423-84176c64269d/go.mod h1:70BFhO60E3e7kq+ssiouwX/HX5DvQ3L6XffdH0S2YfU= github.com/grafana/grafana_plugin_model v0.0.0-20180518082423-84176c64269d/go.mod h1:70BFhO60E3e7kq+ssiouwX/HX5DvQ3L6XffdH0S2YfU=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
@@ -29,6 +33,8 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -38,17 +44,24 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/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 h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.14.0 h1:ArxJuB1NWfPY6r9Gp9gqwplT0Ge7nqv9msgu03lHLmo= google.golang.org/grpc v1.14.0 h1:ArxJuB1NWfPY6r9Gp9gqwplT0Ge7nqv9msgu03lHLmo=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=

View File

@@ -25,7 +25,14 @@
"url": "https://github.com/alexanderzobnin/grafana-zabbix/issues" "url": "https://github.com/alexanderzobnin/grafana-zabbix/issues"
}, },
"devDependencies": { "devDependencies": {
"@types/classnames": "^2.2.6", "@babel/core": "^7.6.4",
"@babel/preset-env": "^7.6.3",
"@babel/preset-react": "^7.6.3",
"@grafana/data": "^6.4.3",
"@grafana/runtime": "^6.4.3",
"@grafana/toolkit": "^6.4.3",
"@grafana/ui": "^6.4.3",
"@types/classnames": "^2.2.9",
"@types/grafana": "github:CorpGlory/types-grafana", "@types/grafana": "github:CorpGlory/types-grafana",
"@types/jest": "^23.1.1", "@types/jest": "^23.1.1",
"@types/jquery": "^3.3.0", "@types/jquery": "^3.3.0",
@@ -34,13 +41,10 @@
"@types/react": "^16.4.6", "@types/react": "^16.4.6",
"@types/react-dom": "^16.0.11", "@types/react-dom": "^16.0.11",
"@types/react-transition-group": "^2.0.15", "@types/react-transition-group": "^2.0.15",
"babel-core": "^6.26.3", "babel-jest": "^24.9.0",
"babel-jest": "^23.6.0", "babel-loader": "^8.0.6",
"babel-loader": "^7.1.2",
"babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"benchmark": "^2.1.4", "benchmark": "^2.1.4",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"clean-webpack-plugin": "^0.1.19", "clean-webpack-plugin": "^0.1.19",
@@ -71,13 +75,13 @@
"react-test-renderer": "^16.7.0", "react-test-renderer": "^16.7.0",
"react-transition-group": "^2.5.2", "react-transition-group": "^2.5.2",
"rst2html": "github:thoward/rst2html#990cb89", "rst2html": "github:thoward/rst2html#990cb89",
"sass-loader": "^7.1.0", "sass-loader": "^8.0.0",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"tether-drop": "^1.4.2", "tether-drop": "^1.4.2",
"ts-jest": "^23.10.5", "ts-jest": "^23.10.5",
"ts-loader": "^4.4.1", "ts-loader": "^6.2.0",
"tslint": "^5.11.0", "tslint": "^5.11.0",
"typescript": "^2.9.2", "typescript": "^3.6.4",
"webpack": "^4.22.0", "webpack": "^4.22.0",
"webpack-cli": "^3.1.2" "webpack-cli": "^3.1.2"
}, },

View File

@@ -3,8 +3,10 @@ package main
import ( import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"encoding/json"
"time" "time"
"github.com/grafana/grafana_plugin_model/go/datasource"
cache "github.com/patrickmn/go-cache" cache "github.com/patrickmn/go-cache"
) )
@@ -30,9 +32,18 @@ func (c *Cache) Get(request string) (interface{}, bool) {
return c.cache.Get(request) return c.cache.Get(request)
} }
// Hash converts the given text string to hash string // HashString converts the given text string to hash string
func Hash(text string) string { func HashString(text string) string {
hash := sha1.New() hash := sha1.New()
hash.Write([]byte(text)) hash.Write([]byte(text))
return hex.EncodeToString(hash.Sum(nil)) return hex.EncodeToString(hash.Sum(nil))
} }
// HashDatasourceInfo converts the given datasource info to hash string
func HashDatasourceInfo(dsInfo *datasource.DatasourceInfo) string {
digester := sha1.New()
if err := json.NewEncoder(digester).Encode(dsInfo); err != nil {
panic(err) // This shouldn't be possible but just in case DatasourceInfo changes
}
return hex.EncodeToString(digester.Sum(nil))
}

View File

@@ -1,3 +1,68 @@
package main package main
// Dummy test file for now import (
"testing"
"github.com/grafana/grafana_plugin_model/go/datasource"
"gotest.tools/assert"
)
func TestHashDatasourceInfo(t *testing.T) {
tests := []struct {
name string
dsInfo *datasource.DatasourceInfo
want string
}{
{
name: "Normal Datasource Info",
dsInfo: &datasource.DatasourceInfo{
Id: 1,
OrgId: 1,
Name: "Zabbix",
Type: "alexanderzobnin-zabbix-datasource",
Url: "https://localhost:3306/zabbix/api_jsonrpc.php",
JsonData: "{}",
DecryptedSecureJsonData: map[string]string{
"username": "grafanaZabbixUser",
"password": "$uper$ecr3t!!!",
},
},
want: "ed161f89179c46d9a578e4d7e92ff95444222e0a",
},
// Can't find a case where the input causes the encoder to fail
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := HashDatasourceInfo(tt.dsInfo)
assert.Equal(t, tt.want, got)
})
}
}
func BenchmarkHashDatasourceInfo(b *testing.B) {
benches := []struct {
name string
dsInfo *datasource.DatasourceInfo
}{
{
"Normal Datasource Info",
&datasource.DatasourceInfo{
Id: 4,
OrgId: 6,
Name: "MyZabbixDatasource",
Type: "alexanderzobnin-zabbix-datasource",
Url: "https://localhost:3306/zabbix/api_jsonrpc.php",
JsonData: `{ "addThresholds": true, "disableReadOnlyUsersAck": true }`,
DecryptedSecureJsonData: map[string]string{
"username": "grafanaZabbixUser",
"password": "$uper$ecr3t!!!",
},
},
},
}
for _, bt := range benches {
b.Run(bt.name, func(b *testing.B) {
HashDatasourceInfo(bt.dsInfo)
})
}
}

View File

@@ -1,7 +1,9 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"fmt"
simplejson "github.com/bitly/go-simplejson" simplejson "github.com/bitly/go-simplejson"
"github.com/grafana/grafana_plugin_model/go/datasource" "github.com/grafana/grafana_plugin_model/go/datasource"
@@ -10,39 +12,95 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
type ZabbixDatasource struct { // ZabbixBackend implements the Grafana backend interface and forwards queries to the ZabbixDatasource
type ZabbixBackend struct {
plugin.NetRPCUnsupportedPlugin plugin.NetRPCUnsupportedPlugin
logger hclog.Logger logger hclog.Logger
datasourceCache *Cache
} }
func (ds *ZabbixDatasource) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) { func (b *ZabbixBackend) newZabbixDatasource() *ZabbixDatasource {
ds := NewZabbixDatasource()
ds.logger = b.logger
return ds
}
// Query receives requests from the Grafana backend. Requests are filtered by query type and sent to the
// applicable ZabbixDatasource.
func (b *ZabbixBackend) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
zabbixDs := b.getCachedDatasource(tsdbReq)
queryType, err := GetQueryType(tsdbReq) queryType, err := GetQueryType(tsdbReq)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dsInfo := tsdbReq.GetDatasource()
ds.logger.Debug("createRequest", "dsInfo", dsInfo)
ds.logger.Debug("createRequest", "queryType", queryType)
switch queryType { switch queryType {
case "zabbixAPI": case "zabbixAPI":
return ds.ZabbixAPIQuery(ctx, tsdbReq) return zabbixDs.ZabbixAPIQuery(ctx, tsdbReq)
case "connectionTest":
return zabbixDs.TestConnection(ctx, tsdbReq)
default: default:
return nil, errors.New("Query is not implemented yet") err = errors.New("Query not implemented")
return BuildErrorResponse(err), nil
} }
} }
func (b *ZabbixBackend) getCachedDatasource(tsdbReq *datasource.DatasourceRequest) *ZabbixDatasource {
dsInfoHash := HashDatasourceInfo(tsdbReq.GetDatasource())
if cachedData, ok := b.datasourceCache.Get(dsInfoHash); ok {
if cachedDS, ok := cachedData.(*ZabbixDatasource); ok {
return cachedDS
}
}
if b.logger.IsDebug() {
dsInfo := tsdbReq.GetDatasource()
b.logger.Debug(fmt.Sprintf("Datasource cache miss (Org %d Id %d '%s' %s)", dsInfo.GetOrgId(), dsInfo.GetId(), dsInfo.GetName(), dsInfoHash))
}
return b.newZabbixDatasource()
}
// GetQueryType determines the query type from a query or list of queries
func GetQueryType(tsdbReq *datasource.DatasourceRequest) (string, error) { func GetQueryType(tsdbReq *datasource.DatasourceRequest) (string, error) {
queryType := "query" queryType := "query"
if len(tsdbReq.Queries) > 0 { if len(tsdbReq.Queries) > 0 {
firstQuery := tsdbReq.Queries[0] firstQuery := tsdbReq.Queries[0]
queryJson, err := simplejson.NewJson([]byte(firstQuery.ModelJson)) queryJSON, err := simplejson.NewJson([]byte(firstQuery.ModelJson))
if err != nil { if err != nil {
return "", err return "", err
} }
queryType = queryJson.Get("queryType").MustString("query") queryType = queryJSON.Get("queryType").MustString("query")
} }
return queryType, nil return queryType, nil
} }
// BuildResponse transforms a Zabbix API response to a DatasourceResponse
func BuildResponse(responseData interface{}) (*datasource.DatasourceResponse, error) {
jsonBytes, err := json.Marshal(responseData)
if err != nil {
return nil, err
}
return &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
RefId: "zabbixAPI",
MetaJson: string(jsonBytes),
},
},
}, nil
}
// BuildErrorResponse creates a QueryResult that forwards an error to the front-end
func BuildErrorResponse(err error) *datasource.DatasourceResponse {
return &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
RefId: "zabbixAPI",
Error: err.Error(),
},
},
}
}

143
pkg/datasource_test.go Normal file
View File

@@ -0,0 +1,143 @@
package main
import (
"testing"
simplejson "github.com/bitly/go-simplejson"
"github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog"
cache "github.com/patrickmn/go-cache"
"gotest.tools/assert"
"gotest.tools/assert/cmp"
)
func TestZabbixBackend_getCachedDatasource(t *testing.T) {
basicDatasourceInfo := &datasource.DatasourceInfo{
Id: 1,
Name: "TestDatasource",
}
basicDatasourceInfoHash := HashDatasourceInfo(basicDatasourceInfo)
modifiedDatasource := NewZabbixDatasource()
modifiedDatasource.authToken = "AB404F1234"
altDatasourceInfo := &datasource.DatasourceInfo{
Id: 2,
Name: "AnotherDatasource",
}
altDatasourceInfoHash := HashDatasourceInfo(altDatasourceInfo)
tests := []struct {
name string
cache *cache.Cache
request *datasource.DatasourceRequest
want *ZabbixDatasource
}{
{
name: "Uncached Datasource (nothing in cache)",
request: &datasource.DatasourceRequest{
Datasource: basicDatasourceInfo,
},
want: NewZabbixDatasource(),
},
{
name: "Uncached Datasource (cache miss)",
cache: cache.NewFrom(cache.NoExpiration, cache.NoExpiration, map[string]cache.Item{
basicDatasourceInfoHash: cache.Item{Object: modifiedDatasource},
}),
request: &datasource.DatasourceRequest{
Datasource: altDatasourceInfo,
},
want: NewZabbixDatasource(),
},
{
name: "Cached Datasource",
cache: cache.NewFrom(cache.NoExpiration, cache.NoExpiration, map[string]cache.Item{
altDatasourceInfoHash: cache.Item{Object: NewZabbixDatasource()},
basicDatasourceInfoHash: cache.Item{Object: modifiedDatasource},
}),
request: &datasource.DatasourceRequest{
Datasource: basicDatasourceInfo,
},
want: modifiedDatasource,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.cache == nil {
tt.cache = cache.New(cache.NoExpiration, cache.NoExpiration)
}
b := &ZabbixBackend{
logger: hclog.New(&hclog.LoggerOptions{
Name: "TestZabbixBackend_getCachedDatasource",
Level: hclog.LevelFromString("DEBUG"),
}),
datasourceCache: &Cache{cache: tt.cache},
}
got := b.getCachedDatasource(tt.request)
// Only checking the authToken, being the easiest value to, and guarantee equality for
assert.Equal(t, tt.want.authToken, got.authToken)
})
}
}
func TestBuildResponse(t *testing.T) {
jsonData := simplejson.New()
jsonData.Set("testing", []int{5, 12, 75})
tests := []struct {
name string
responseData interface{}
want *datasource.DatasourceResponse
wantErr string
}{
{
name: "simplejson Response",
responseData: jsonData,
want: &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
RefId: "zabbixAPI",
MetaJson: `{"testing":[5,12,75]}`,
},
},
},
},
{
name: "Connetion Status Response",
responseData: connectionTestResponse{
ZabbixVersion: "2.4",
DbConnectorStatus: &dbConnectionStatus{
DsType: "mysql",
DsName: "MyDatabase",
},
},
want: &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
RefId: "zabbixAPI",
MetaJson: `{"zabbixVersion":"2.4","dbConnectorStatus":{"dsType":"mysql","dsName":"MyDatabase"}}`,
},
},
},
},
{
name: "Unmarshalable",
responseData: 2i,
wantErr: "json: unsupported type: complex128",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := BuildResponse(tt.responseData)
if tt.wantErr != "" {
assert.Error(t, err, tt.wantErr)
assert.Assert(t, cmp.Nil(got))
return
}
assert.NilError(t, err)
assert.DeepEqual(t, got, tt.want)
})
}
}

View File

@@ -1 +1,11 @@
package main package main
type connectionTestResponse struct {
ZabbixVersion string `json:"zabbixVersion"`
DbConnectorStatus *dbConnectionStatus `json:"dbConnectorStatus"`
}
type dbConnectionStatus struct {
DsType string `json:"dsType"`
DsName string `json:"dsName"`
}

View File

@@ -1,6 +1,8 @@
package main package main
import ( import (
"time"
"github.com/grafana/grafana_plugin_model/go/datasource" "github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog" hclog "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" plugin "github.com/hashicorp/go-plugin"
@@ -22,8 +24,9 @@ func main() {
MagicCookieValue: "datasource", MagicCookieValue: "datasource",
}, },
Plugins: map[string]plugin.Plugin{ Plugins: map[string]plugin.Plugin{
"zabbix-backend-datasource": &datasource.DatasourcePluginImpl{Plugin: &ZabbixDatasource{ "zabbix-backend-datasource": &datasource.DatasourcePluginImpl{Plugin: &ZabbixBackend{
logger: pluginLogger, datasourceCache: NewCache(10*time.Minute, 10*time.Minute),
logger: pluginLogger,
}}, }},
}, },

View File

@@ -15,35 +15,47 @@ import (
simplejson "github.com/bitly/go-simplejson" simplejson "github.com/bitly/go-simplejson"
"github.com/grafana/grafana_plugin_model/go/datasource" "github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp" "golang.org/x/net/context/ctxhttp"
) )
var httpClient = &http.Client{ // ZabbixDatasource stores state about a specific datasource and provides methods to make
Transport: &http.Transport{ // requests to the Zabbix API
TLSClientConfig: &tls.Config{ type ZabbixDatasource struct {
Renegotiation: tls.RenegotiateFreelyAsClient, queryCache *Cache
}, logger hclog.Logger
Proxy: http.ProxyFromEnvironment, httpClient *http.Client
Dial: (&net.Dialer{ authToken string
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
},
Timeout: time.Duration(time.Second * 30),
} }
var queryCache = NewCache(10*time.Minute, 10*time.Minute) // NewZabbixDatasource returns an initialized ZabbixDatasource
func NewZabbixDatasource() *ZabbixDatasource {
var zabbixAuth string = "" return &ZabbixDatasource{
queryCache: NewCache(10*time.Minute, 10*time.Minute),
httpClient: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateFreelyAsClient,
},
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
},
Timeout: time.Duration(time.Second * 30),
},
}
}
// ZabbixAPIQuery handles query requests to Zabbix
func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) { func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
result, queryExistInCache := queryCache.Get(Hash(tsdbReq.String())) result, queryExistInCache := ds.queryCache.Get(HashString(tsdbReq.String()))
if !queryExistInCache { if !queryExistInCache {
dsInfo := tsdbReq.GetDatasource() dsInfo := tsdbReq.GetDatasource()
@@ -52,7 +64,7 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou
for _, query := range tsdbReq.Queries { for _, query := range tsdbReq.Queries {
json, err := simplejson.NewJson([]byte(query.ModelJson)) json, err := simplejson.NewJson([]byte(query.ModelJson))
apiMethod := json.GetPath("target", "method").MustString() apiMethod := json.GetPath("target", "method").MustString()
apiParams := json.GetPath("target", "params") apiParams := json.GetPath("target", "params").MustMap()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -69,11 +81,11 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou
jsonQuery := jsonQueries[0].Get("target") jsonQuery := jsonQueries[0].Get("target")
apiMethod := jsonQuery.Get("method").MustString() apiMethod := jsonQuery.Get("method").MustString()
apiParams := jsonQuery.Get("params") apiParams := jsonQuery.Get("params").MustMap()
var err error response, err := ds.ZabbixRequest(ctx, dsInfo, apiMethod, apiParams)
result, err = ds.ZabbixRequest(ctx, dsInfo, apiMethod, apiParams) ds.queryCache.Set(HashString(tsdbReq.String()), response)
queryCache.Set(Hash(tsdbReq.String()), result) result = response
if err != nil { if err != nil {
ds.logger.Debug("ZabbixAPIQuery", "error", err) ds.logger.Debug("ZabbixAPIQuery", "error", err)
return nil, errors.New("ZabbixAPIQuery is not implemented yet") return nil, errors.New("ZabbixAPIQuery is not implemented yet")
@@ -83,44 +95,55 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou
resultByte, _ := result.(*simplejson.Json).MarshalJSON() resultByte, _ := result.(*simplejson.Json).MarshalJSON()
ds.logger.Debug("ZabbixAPIQuery", "result", string(resultByte)) ds.logger.Debug("ZabbixAPIQuery", "result", string(resultByte))
return ds.BuildResponse(result.(*simplejson.Json)) return BuildResponse(result)
} }
func (ds *ZabbixDatasource) BuildResponse(result *simplejson.Json) (*datasource.DatasourceResponse, error) { // TestConnection checks authentication and version of the Zabbix API and returns that info
resultByte, err := result.MarshalJSON() func (ds *ZabbixDatasource) TestConnection(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
dsInfo := tsdbReq.GetDatasource()
auth, err := ds.loginWithDs(ctx, dsInfo)
if err != nil { if err != nil {
return nil, err return BuildErrorResponse(fmt.Errorf("Authentication failed: %w", err)), nil
}
ds.authToken = auth
response, err := ds.zabbixAPIRequest(ctx, dsInfo.GetUrl(), "apiinfo.version", map[string]interface{}{}, "")
if err != nil {
ds.logger.Debug("TestConnection", "error", err)
return BuildErrorResponse(fmt.Errorf("Version check failed: %w", err)), nil
} }
return &datasource.DatasourceResponse{ resultByte, _ := response.MarshalJSON()
Results: []*datasource.QueryResult{ ds.logger.Debug("TestConnection", "result", string(resultByte))
&datasource.QueryResult{
RefId: "zabbixAPI", testResponse := connectionTestResponse{
MetaJson: string(resultByte), ZabbixVersion: response.MustString(),
}, }
},
}, nil return BuildResponse(testResponse)
} }
func (ds *ZabbixDatasource) ZabbixRequest(ctx context.Context, dsInfo *datasource.DatasourceInfo, method string, params *simplejson.Json) (*simplejson.Json, error) { // ZabbixRequest checks authentication and makes a request to the Zabbix API
func (ds *ZabbixDatasource) ZabbixRequest(ctx context.Context, dsInfo *datasource.DatasourceInfo, method string, params map[string]interface{}) (*simplejson.Json, error) {
zabbixURL := dsInfo.GetUrl() zabbixURL := dsInfo.GetUrl()
var result *simplejson.Json var result *simplejson.Json
var err error var err error
for attempt := 0; attempt <= 3; attempt++ { for attempt := 0; attempt <= 3; attempt++ {
if zabbixAuth == "" { if ds.authToken == "" {
// Authenticate // Authenticate
zabbixAuth, err = ds.loginWithDs(ctx, dsInfo) ds.authToken, err = ds.loginWithDs(ctx, dsInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
result, err = ds.zabbixAPIRequest(ctx, zabbixURL, method, params, zabbixAuth) result, err = ds.zabbixAPIRequest(ctx, zabbixURL, method, params, ds.authToken)
if err == nil || (err != nil && !isNotAuthorized(err.Error())) { if err == nil || (err != nil && !isNotAuthorized(err.Error())) {
break break
} else { } else {
zabbixAuth = "" ds.authToken = ""
} }
} }
return result, err return result, err
@@ -162,12 +185,7 @@ func (ds *ZabbixDatasource) login(ctx context.Context, apiURL string, username s
"user": username, "user": username,
"password": password, "password": password,
} }
paramsJSON, err := json.Marshal(params) auth, err := ds.zabbixAPIRequest(ctx, apiURL, "user.login", params, "")
if err != nil {
return "", err
}
data, _ := simplejson.NewJson(paramsJSON)
auth, err := ds.zabbixAPIRequest(ctx, apiURL, "user.login", data, "")
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -175,7 +193,7 @@ func (ds *ZabbixDatasource) login(ctx context.Context, apiURL string, username s
return auth.MustString(), nil return auth.MustString(), nil
} }
func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiURL string, method string, params *simplejson.Json, auth string) (*simplejson.Json, error) { func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiURL string, method string, params map[string]interface{}, auth string) (*simplejson.Json, error) {
zabbixURL, err := url.Parse(apiURL) zabbixURL, err := url.Parse(apiURL)
// TODO: inject auth token (obtain from 'user.login' first) // TODO: inject auth token (obtain from 'user.login' first)
@@ -211,7 +229,7 @@ func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiURL string,
Body: rc, Body: rc,
} }
response, err := makeHTTPRequest(ctx, req) response, err := makeHTTPRequest(ctx, ds.httpClient, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -234,7 +252,7 @@ func handleAPIResult(response []byte) (*simplejson.Json, error) {
return jsonResult, nil return jsonResult, nil
} }
func makeHTTPRequest(ctx context.Context, req *http.Request) ([]byte, error) { func makeHTTPRequest(ctx context.Context, httpClient *http.Client, req *http.Request) ([]byte, error) {
res, err := ctxhttp.Do(ctx, httpClient, req) res, err := ctxhttp.Do(ctx, httpClient, req)
if err != nil { if err != nil {
return nil, err return nil, err

1
pkg/zabbix_api_test.go Normal file
View File

@@ -0,0 +1 @@
package main

View File

@@ -9,13 +9,29 @@ import dataProcessor from './dataProcessor';
import responseHandler from './responseHandler'; import responseHandler from './responseHandler';
import { Zabbix } from './zabbix/zabbix'; import { Zabbix } from './zabbix/zabbix';
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore'; import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore';
import {
DataSourceApi,
DataSourceInstanceSettings,
} from '@grafana/ui';
import { BackendSrv, DataSourceSrv } from '@grafana/runtime';
import { ZabbixAlertingService } from './zabbixAlerting.service';
import { ZabbixConnectionTestQuery, ZabbixConnectionInfo, TemplateSrv, TSDBResponse } from './types';
const DEFAULT_ZABBIX_VERSION = 3; const DEFAULT_ZABBIX_VERSION = 3;
export class ZabbixDatasource { export class ZabbixDatasource extends DataSourceApi {
/** @ngInject */ /**
* @ngInject
* @param {DataSourceInstanceSettings} instanceSettings
* @param {TemplateSrv} templateSrv
* @param {BackendSrv} backendSrv
* @param {DataSourceSrv} datasourceSrv
* @param {ZabbixAlertingService} zabbixAlertingSrv
*/
constructor(instanceSettings, templateSrv, backendSrv, datasourceSrv, zabbixAlertingSrv) { constructor(instanceSettings, templateSrv, backendSrv, datasourceSrv, zabbixAlertingSrv) {
super(instanceSettings);
this.type = 'zabbix';
this.templateSrv = templateSrv; this.templateSrv = templateSrv;
this.backendSrv = backendSrv; this.backendSrv = backendSrv;
this.zabbixAlertingSrv = zabbixAlertingSrv; this.zabbixAlertingSrv = zabbixAlertingSrv;
@@ -185,11 +201,26 @@ export class ZabbixDatasource {
tsdbRequestData.to = options.range.to.valueOf().toString(); tsdbRequestData.to = options.range.to.valueOf().toString();
} }
return this.backendSrv.datasourceRequest({ return this.backendSrv.post('/api/tsdb/query', tsdbRequestData);
url: '/api/tsdb/query', }
method: 'POST',
data: tsdbRequestData /**
}); * @returns {Promise<TSDBResponse>}
*/
doTSDBConnectionTest() {
/**
* @type {{ queries: ZabbixConnectionTestQuery[] }}
*/
const tsdbRequestData = {
queries: [
{
datasourceId: this.datasourceId,
queryType: 'connectionTest'
}
]
};
return this.backendSrv.post('/api/tsdb/query', tsdbRequestData);
} }
/** /**
@@ -385,10 +416,13 @@ export class ZabbixDatasource {
/** /**
* Test connection to Zabbix API and external history DB. * Test connection to Zabbix API and external history DB.
*/ */
testDatasource() { async testDatasource() {
return this.zabbix.testDataSource() try {
.then(result => { const result = await this.doTSDBConnectionTest();
const { zabbixVersion, dbConnectorStatus } = result; /**
* @type {ZabbixConnectionInfo}
*/
const { zabbixVersion, dbConnectorStatus } = result.results["zabbixAPI"].meta;
let message = `Zabbix API version: ${zabbixVersion}`; let message = `Zabbix API version: ${zabbixVersion}`;
if (dbConnectorStatus) { if (dbConnectorStatus) {
message += `, DB connector type: ${dbConnectorStatus.dsType}`; message += `, DB connector type: ${dbConnectorStatus.dsType}`;
@@ -398,27 +432,30 @@ export class ZabbixDatasource {
title: "Success", title: "Success",
message: message message: message
}; };
}) }
.catch(error => { catch (error) {
if (error instanceof ZabbixAPIError) { if (error instanceof ZabbixAPIError) {
return { return {
status: "error", status: "error",
title: error.message, title: error.message,
message: error.message message: error.message
}; };
} else if (error.data && error.data.message) { }
else if (error.data && error.data.message) {
return { return {
status: "error", status: "error",
title: "Connection failed", title: "Zabbix Client Error",
message: "Connection failed: " + error.data.message message: error.data.message
}; };
} else if (typeof(error) === 'string') { }
else if (typeof (error) === 'string') {
return { return {
status: "error", status: "error",
title: "Connection failed", title: "Unknown Error",
message: "Connection failed: " + error message: error
}; };
} else { }
else {
console.log(error); console.log(error);
return { return {
status: "error", status: "error",
@@ -426,7 +463,7 @@ export class ZabbixDatasource {
message: "Could not connect to given url" message: "Could not connect to given url"
}; };
} }
}); }
} }
/** /**

View File

@@ -1,4 +1,5 @@
import _ from 'lodash'; import _ from 'lodash';
import { ZabbixMetricsQuery } from './types';
/** /**
* Query format migration. * Query format migration.
@@ -19,7 +20,7 @@ export function isGrafana2target(target) {
} }
} }
export function migrateFrom2To3version(target) { export function migrateFrom2To3version(target: ZabbixMetricsQuery) {
target.group.filter = target.group.name === "*" ? "/.*/" : target.group.name; target.group.filter = target.group.name === "*" ? "/.*/" : target.group.name;
target.host.filter = target.host.name === "*" ? convertToRegex(target.hostFilter) : target.host.name; target.host.filter = target.host.name === "*" ? convertToRegex(target.hostFilter) : target.host.name;
target.application.filter = target.application.name === "*" ? "" : target.application.name; target.application.filter = target.application.name === "*" ? "" : target.application.name;

View File

@@ -1,4 +1,4 @@
import { loadPluginCss } from 'grafana/app/plugins/sdk'; import { loadPluginCss } from '@grafana/runtime';
import { ZabbixDatasource } from './datasource'; import { ZabbixDatasource } from './datasource';
import { ZabbixQueryController } from './query.controller'; import { ZabbixQueryController } from './query.controller';
import { ZabbixDSConfigController } from './config.controller'; import { ZabbixDSConfigController } from './config.controller';

View File

@@ -0,0 +1,85 @@
import { DataQuery } from '@grafana/ui';
export interface ZabbixConnectionInfo {
zabbixVersion: string;
dbConnectorStatus: {
dsType: string;
dsName: string;
};
}
export interface ZabbixConnectionTestQuery {
datasourceId: number;
queryType: string;
}
export interface ZabbixMetricsQuery extends DataQuery {
triggers: { minSeverity: string; acknowledged: boolean; count: number; };
queryType: string;
datasourceId: number;
functions: { name: string; params: any; def: { name: string; params: any; } }[];
options: any;
textFilter: string;
mode: number;
itemids: number[];
useCaptureGroups: boolean;
group: { filter: string; name: string; };
host: { filter: string; name: string; };
hostFilter: string;
application: { filter: string; name: string; };
item: { filter: string; name: string; };
itemFilter: string;
}
// export { TemplateSrv } from 'grafana/app/features/templating/template_srv';
// export { DashboardSrv } from 'grafana/app/features/dashboard/dashboard_srv';
// The paths of these files have moved around in Grafana and they don't resolve properly
// either. Safer not to bother trying to import them just for type hinting.
export interface TemplateSrv {
variables: {
name: string;
};
highlightVariablesAsHtml(str: any): any;
replace(target: any, scopedVars?: any, format?: any): any;
}
export interface DashboardSrv {
dash: any
}
// Grafana types from backend code
type RowValues = object[];
type TimePoint = [number?, number?];
type TimeSeriesPoints = TimePoint[];
type TimeSeriesSlice = TimeSeries[];
interface TimeSeries {
name: string;
points: TimeSeriesPoints;
tags: { [key: string]: string };
}
interface TableColumn {
text: string;
}
interface Table {
columns: TableColumn[];
rows: RowValues[];
}
interface QueryResult {
error: string;
refId: string;
meta: any;
series: TimeSeriesSlice[];
tables: Table[];
}
export interface TSDBResponse {
results: { [key: string]: QueryResult };
message: string;
}

View File

@@ -110,37 +110,37 @@ export class Zabbix {
} }
``` ```
*/ */
testDataSource() { // testDataSource() {
let zabbixVersion; // let zabbixVersion;
let dbConnectorStatus; // let dbConnectorStatus;
return this.getVersion() // return this.getVersion()
.then(version => { // .then(version => {
zabbixVersion = version; // zabbixVersion = version;
return this.login(); // return this.login();
}) // })
.then(() => { // .then(() => {
if (this.enableDirectDBConnection) { // if (this.enableDirectDBConnection) {
return this.dbConnector.testDataSource(); // return this.dbConnector.testDataSource();
} else { // } else {
return Promise.resolve(); // return Promise.resolve();
} // }
}) // })
.catch(error => { // .catch(error => {
if (error instanceof ZabbixNotImplemented) { // if (error instanceof ZabbixNotImplemented) {
return Promise.resolve(); // return Promise.resolve();
} // }
return Promise.reject(error); // return Promise.reject(error);
}) // })
.then(testResult => { // .then(testResult => {
if (testResult) { // if (testResult) {
dbConnectorStatus = { // dbConnectorStatus = {
dsType: this.dbConnector.datasourceTypeName, // dsType: this.dbConnector.datasourceTypeName,
dsName: this.dbConnector.datasourceName // dsName: this.dbConnector.datasourceName
}; // };
} // }
return { zabbixVersion, dbConnectorStatus }; // return { zabbixVersion, dbConnectorStatus };
}); // });
} // }
getItemsFromTarget(target, options) { getItemsFromTarget(target, options) {
let parts = ['group', 'host', 'application', 'item']; let parts = ['group', 'host', 'application', 'item'];

View File

@@ -2,9 +2,11 @@ import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import angular from 'angular'; import angular from 'angular';
class ZabbixAlertingService { export class ZabbixAlertingService {
/** @ngInject */ /**
* @ngInject
*/
constructor(dashboardSrv) { constructor(dashboardSrv) {
this.dashboardSrv = dashboardSrv; this.dashboardSrv = dashboardSrv;
} }

View File

@@ -18,6 +18,14 @@ jest.mock('angular', () => {
}; };
}, {virtual: true}); }, {virtual: true});
jest.mock('grafana/app/features/templating/template_srv', () => {
return {};
}, {virtual: true});
jest.mock('grafana/app/features/dashboard/dashboard_srv', () => {
return {};
}, {virtual: true});
jest.mock('grafana/app/core/core_module', () => { jest.mock('grafana/app/core/core_module', () => {
return { return {
directive: function() {}, directive: function() {},
@@ -28,7 +36,6 @@ let mockPanelCtrl = PanelCtrl;
jest.mock('grafana/app/plugins/sdk', () => { jest.mock('grafana/app/plugins/sdk', () => {
return { return {
QueryCtrl: null, QueryCtrl: null,
loadPluginCss: () => {},
PanelCtrl: mockPanelCtrl PanelCtrl: mockPanelCtrl
}; };
}, {virtual: true}); }, {virtual: true});
@@ -74,9 +81,9 @@ jest.mock('grafana/app/core/config', () => {
jest.mock('jquery', () => 'module not found', {virtual: true}); jest.mock('jquery', () => 'module not found', {virtual: true});
jest.mock('@grafana/ui', () => { jest.mock('@grafana/ui');
return {};
}, {virtual: true}); jest.mock('@grafana/runtime');
// Required for loading angularjs // Required for loading angularjs
let dom = new JSDOM('<html><head><script></script></head><body></body></html>'); let dom = new JSDOM('<html><head><script></script></head><body></body></html>');

View File

@@ -28,7 +28,7 @@ module.exports = {
externals: [ externals: [
// remove the line below if you don't want to use builtin versions // remove the line below if you don't want to use builtin versions
'jquery', 'lodash', 'moment', 'angular', 'jquery', 'lodash', 'moment', 'angular',
'react', 'react-dom', '@grafana/ui', 'react', 'react-dom', '@grafana/ui', '@grafana/runtime',
function (context, request, callback) { function (context, request, callback) {
var prefix = 'grafana/'; var prefix = 'grafana/';
if (request.indexOf(prefix) === 0) { if (request.indexOf(prefix) === 0) {
@@ -63,7 +63,7 @@ module.exports = {
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
query: { query: {
presets: ['babel-preset-env'] presets: ['@babel/preset-env']
} }
} }
}, },

6354
yarn.lock

File diff suppressed because it is too large Load Diff