Skip to content
This repository was archived by the owner on Dec 11, 2022. It is now read-only.

Commit 1fad0ea

Browse files
committed
Add alert to bigquery plugin
1 parent 5bacd1f commit 1fad0ea

15 files changed

+1013
-12
lines changed

dist/README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[![Code Climate](https://codeclimate.com/github/doitintl/bigquery-grafana/badges/gpa.svg)](https://codeclimate.com/github/doitintl/bigquery-grafana/coverage)
66
[![Issue Count](https://codeclimate.com/github/doitintl/bigquery-grafana/badges/issue_count.svg)](https://codeclimate.com/github/doitintl/bigquery-grafana)
77
[![CodeCpv](https://codecov.io/gh/doitintl/bigquery-grafana/branch/master/graph/badge.svg)](https://codecov.io/gh/doitintl/bigquery-grafana/)
8+
[![Automated Release Notes by gren](https://img.shields.io/badge/%F0%9F%A4%96-release%20notes-00B2EE.svg)](https://github-tools.github.io/github-release-notes/)
89
## Status: Production Ready
910
# BigQuery DataSource for Grafana
1011

@@ -27,10 +28,6 @@ There are multiple ways to install bigquery-grafana. See [INSTALL](https://doiti
2728
* Partitioned Tables
2829
* Granular slot allocation (Running queries in a project with flat-rate pricing)
2930

30-
### Limitations:
31-
32-
* Alerts are not yet supported due to [#6841](https://github.com/grafana/grafana/issues/6841)
33-
3431
**Plugin Demo:**
3532

3633
![plugin demo](https://raw.githubusercontent.com/doitintl/bigquery-grafana/master/img/grafana-bigquery-demo.gif)
18.7 MB
Binary file not shown.
18.7 MB
Binary file not shown.
18.7 MB
Binary file not shown.

dist/module.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59623,6 +59623,9 @@ var _response_parser2 = _interopRequireDefault(_response_parser);
5962359623

5962459624
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
5962559625

59626+
// import { validate } from "@babel/types";
59627+
// import { sheets } from "googleapis/build/src/apis/sheets";
59628+
// import { countBy, size } from "lodash-es";
5962659629
var Shifted = "_shifted";
5962759630

5962859631
function sleep(ms) {
@@ -62053,8 +62056,7 @@ function () {
6205362056
var r = [];
6205462057
(0, _lodashEs.each)(ser, function (v) {
6205562058
for (var i = 0; i < v.length; i++) {
62056-
var val = ResponseParser._convertValues(v[i].v, columns[i].type);
62057-
62059+
var val = v[i].v ? ResponseParser._convertValues(v[i].v, columns[i].type) : "";
6205862060
r.push(val);
6205962061
}
6206062062
});

dist/module.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/plugin.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"metrics": true,
66
"tables": true,
77
"annotations": true,
8-
"alerting": false,
8+
"alerting": true,
9+
"backend": true,
10+
"executable": "doitintl-bigquery-datasource",
911

1012
"queryOptions": {
1113
"maxDataPoints": true
@@ -26,7 +28,7 @@
2628
{"name": "GitHub", "url": "https://github.com/doitintl/bigquery-grafana"},
2729
{"name": "MIT License", "url": "https://github.com/doitintl/bigquery-grafana/blob/master/LICENSE.md"}
2830
],
29-
"version": "1.0.7"
31+
"version": "1.0.8"
3032
},
3133
"routes": [
3234
{

go.mod

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//module github.com/repo/module
2+
module github.com/grafana/simple-datasource-backend
3+
4+
go 1.14
5+
6+
require (
7+
cloud.google.com/go v0.58.0 // indirect
8+
cloud.google.com/go/bigquery v1.8.0
9+
github.com/bitly/go-simplejson v0.5.0
10+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
11+
github.com/go-delve/delve v1.4.1 // indirect
12+
github.com/googleapis/gax-go v1.0.3 // indirect
13+
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
14+
github.com/grafana/grafana-plugin-sdk-go v0.67.0
15+
github.com/hashicorp/go-hclog v0.9.2
16+
github.com/hashicorp/go-plugin v1.2.2
17+
github.com/kr/pretty v0.1.0 // indirect
18+
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
19+
golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect
20+
golang.org/x/tools v0.0.0-20200612220849-54c614fe050c // indirect
21+
//golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
22+
google.golang.org/api v0.26.0
23+
google.golang.org/genproto v0.0.0-20200612171551-7676ae05be11 // indirect
24+
)

go.sum

Lines changed: 529 additions & 0 deletions
Large diffs are not rendered by default.

pkg/datasource.go

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
package main
2+
3+
import (
4+
"crypto/tls"
5+
"encoding/json"
6+
"fmt"
7+
"io/ioutil"
8+
"net"
9+
"net/http"
10+
"strings"
11+
"time"
12+
13+
simplejson "github.com/bitly/go-simplejson"
14+
"github.com/grafana/grafana-plugin-model/go/datasource"
15+
hclog "github.com/hashicorp/go-hclog"
16+
plugin "github.com/hashicorp/go-plugin"
17+
"golang.org/x/net/context"
18+
"golang.org/x/net/context/ctxhttp"
19+
)
20+
21+
type BQDatasource struct {
22+
plugin.NetRPCUnsupportedPlugin
23+
logger hclog.Logger
24+
}
25+
26+
func (t *BQDatasource) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
27+
t.logger.Debug("Query", "datasource", tsdbReq.Datasource.Name, "TimeRange", tsdbReq.TimeRange)
28+
29+
remoteDsReq, err := t.createRequest(tsdbReq)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
res, err := ctxhttp.Do(ctx, httpClient, remoteDsReq.req)
35+
if err != nil {
36+
return nil, err
37+
}
38+
defer res.Body.Close()
39+
40+
if res.StatusCode != http.StatusOK {
41+
return nil, fmt.Errorf("invalid status code. status: %v", res.Status)
42+
}
43+
44+
body, err := ioutil.ReadAll(res.Body)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
switch remoteDsReq.queryType {
50+
case "search":
51+
return t.parseSearchResponse(body)
52+
default:
53+
return t.parseQueryResponse(remoteDsReq.queries, body)
54+
}
55+
}
56+
57+
func (t *BQDatasource) createRequest(tsdbReq *datasource.DatasourceRequest) (*remoteDatasourceRequest, error) {
58+
jQueries := make([]*simplejson.Json, 0)
59+
for _, query := range tsdbReq.Queries {
60+
json, err := simplejson.NewJson([]byte(query.ModelJson))
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
jQueries = append(jQueries, json)
66+
}
67+
68+
queryType := "query"
69+
if len(jQueries) > 0 {
70+
queryType = jQueries[0].Get("queryType").MustString("query")
71+
}
72+
73+
t.logger.Debug("createRequest", "queryType", queryType)
74+
75+
payload := simplejson.New()
76+
77+
switch queryType {
78+
case "search":
79+
payload.Set("target", jQueries[0].Get("target").MustString())
80+
default:
81+
payload.SetPath([]string{"range", "to"}, tsdbReq.TimeRange.ToRaw)
82+
payload.SetPath([]string{"range", "from"}, tsdbReq.TimeRange.FromRaw)
83+
payload.Set("targets", jQueries)
84+
}
85+
86+
rbody, err := payload.MarshalJSON()
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
url := tsdbReq.Datasource.Url + "/" + queryType
92+
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(string(rbody)))
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
req.Header.Add("Content-Type", "application/json")
98+
99+
return &remoteDatasourceRequest{
100+
queryType: queryType,
101+
req: req,
102+
queries: jQueries,
103+
}, nil
104+
}
105+
106+
func (t *BQDatasource) parseQueryResponse(queries []*simplejson.Json, body []byte) (*datasource.DatasourceResponse, error) {
107+
response := &datasource.DatasourceResponse{}
108+
responseBody := []TargetResponseDTO{}
109+
err := json.Unmarshal(body, &responseBody)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
for i, r := range responseBody {
115+
refId := r.Target
116+
117+
if len(queries) > i {
118+
refId = queries[i].Get("refId").MustString()
119+
}
120+
121+
qr := datasource.QueryResult{
122+
RefId: refId,
123+
Series: make([]*datasource.TimeSeries, 0),
124+
Tables: make([]*datasource.Table, 0),
125+
}
126+
127+
if len(r.Columns) > 0 {
128+
table := datasource.Table{
129+
Columns: make([]*datasource.TableColumn, 0),
130+
Rows: make([]*datasource.TableRow, 0),
131+
}
132+
133+
for _, c := range r.Columns {
134+
table.Columns = append(table.Columns, &datasource.TableColumn{
135+
Name: c.Text,
136+
})
137+
}
138+
139+
for _, row := range r.Rows {
140+
values := make([]*datasource.RowValue, 0)
141+
142+
for i, cell := range row {
143+
rv := datasource.RowValue{}
144+
145+
switch r.Columns[i].Type {
146+
case "time":
147+
if timeValue, ok := cell.(float64); ok {
148+
rv.Int64Value = int64(timeValue)
149+
}
150+
rv.Kind = datasource.RowValue_TYPE_INT64
151+
case "number":
152+
if numberValue, ok := cell.(float64); ok {
153+
rv.Int64Value = int64(numberValue)
154+
}
155+
rv.Kind = datasource.RowValue_TYPE_INT64
156+
case "string":
157+
if stringValue, ok := cell.(string); ok {
158+
rv.StringValue = stringValue
159+
}
160+
rv.Kind = datasource.RowValue_TYPE_STRING
161+
default:
162+
t.logger.Debug(fmt.Sprintf("failed to parse value %v of type %T", cell, cell))
163+
}
164+
165+
values = append(values, &rv)
166+
}
167+
168+
table.Rows = append(table.Rows, &datasource.TableRow{Values: values})
169+
}
170+
171+
qr.Tables = append(qr.Tables, &table)
172+
} else {
173+
serie := &datasource.TimeSeries{Name: r.Target}
174+
175+
for _, p := range r.DataPoints {
176+
serie.Points = append(serie.Points, &datasource.Point{
177+
Timestamp: int64(p[1]),
178+
Value: p[0],
179+
})
180+
}
181+
182+
qr.Series = append(qr.Series, serie)
183+
}
184+
185+
response.Results = append(response.Results, &qr)
186+
}
187+
188+
return response, nil
189+
}
190+
191+
func (t *BQDatasource) parseSearchResponse(body []byte) (*datasource.DatasourceResponse, error) {
192+
jBody, err := simplejson.NewJson(body)
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
metricCount := len(jBody.MustArray())
198+
table := datasource.Table{
199+
Columns: []*datasource.TableColumn{
200+
&datasource.TableColumn{Name: "text"},
201+
},
202+
Rows: make([]*datasource.TableRow, 0),
203+
}
204+
205+
for n := 0; n < metricCount; n++ {
206+
values := make([]*datasource.RowValue, 0)
207+
jm := jBody.GetIndex(n)
208+
209+
if text, found := jm.CheckGet("text"); found {
210+
values = append(values, &datasource.RowValue{
211+
Kind: datasource.RowValue_TYPE_STRING,
212+
StringValue: text.MustString(),
213+
})
214+
values = append(values, &datasource.RowValue{
215+
Kind: datasource.RowValue_TYPE_INT64,
216+
Int64Value: jm.Get("value").MustInt64(),
217+
})
218+
219+
if len(table.Columns) == 1 {
220+
table.Columns = append(table.Columns, &datasource.TableColumn{Name: "value"})
221+
}
222+
} else {
223+
values = append(values, &datasource.RowValue{
224+
Kind: datasource.RowValue_TYPE_STRING,
225+
StringValue: jm.MustString(),
226+
})
227+
}
228+
229+
table.Rows = append(table.Rows, &datasource.TableRow{Values: values})
230+
}
231+
232+
return &datasource.DatasourceResponse{
233+
Results: []*datasource.QueryResult{
234+
&datasource.QueryResult{
235+
RefId: "search",
236+
Tables: []*datasource.Table{&table},
237+
},
238+
},
239+
}, nil
240+
}
241+
242+
var httpClient = &http.Client{
243+
Transport: &http.Transport{
244+
TLSClientConfig: &tls.Config{
245+
Renegotiation: tls.RenegotiateFreelyAsClient,
246+
},
247+
Proxy: http.ProxyFromEnvironment,
248+
Dial: (&net.Dialer{
249+
Timeout: 30 * time.Second,
250+
KeepAlive: 30 * time.Second,
251+
DualStack: true,
252+
}).Dial,
253+
TLSHandshakeTimeout: 10 * time.Second,
254+
ExpectContinueTimeout: 1 * time.Second,
255+
MaxIdleConns: 100,
256+
IdleConnTimeout: 90 * time.Second,
257+
},
258+
Timeout: time.Duration(time.Second * 30),
259+
}

0 commit comments

Comments
 (0)