mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
stackdriver: better error handling and show query metadata
If the Stackdriver returns an error, show that error in the query editor. Also, allow the user to see the raw querystring that was sent to google (for troubleshooting).
This commit is contained in:
parent
2683699ab4
commit
0b5783563e
@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/pluginproxy"
|
"github.com/grafana/grafana/pkg/api/pluginproxy"
|
||||||
"github.com/grafana/grafana/pkg/components/null"
|
"github.com/grafana/grafana/pkg/components/null"
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
@ -28,7 +29,8 @@ var slog log.Logger
|
|||||||
|
|
||||||
// StackdriverExecutor executes queries for the Stackdriver datasource
|
// StackdriverExecutor executes queries for the Stackdriver datasource
|
||||||
type StackdriverExecutor struct {
|
type StackdriverExecutor struct {
|
||||||
HTTPClient *http.Client
|
httpClient *http.Client
|
||||||
|
dsInfo *models.DataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStackdriverExecutor initializes a http client
|
// NewStackdriverExecutor initializes a http client
|
||||||
@ -39,7 +41,8 @@ func NewStackdriverExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &StackdriverExecutor{
|
return &StackdriverExecutor{
|
||||||
HTTPClient: httpClient,
|
httpClient: httpClient,
|
||||||
|
dsInfo: dsInfo,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,44 +65,7 @@ func (e *StackdriverExecutor) Query(ctx context.Context, dsInfo *models.DataSour
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, query := range queries {
|
for _, query := range queries {
|
||||||
req, err := e.createRequest(ctx, dsInfo)
|
queryRes, err := e.executeQuery(ctx, query, tsdbQuery)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.URL.RawQuery = query.Params.Encode()
|
|
||||||
slog.Info("tsdbQuery", "req.URL.RawQuery", req.URL.RawQuery)
|
|
||||||
|
|
||||||
httpClient, err := dsInfo.GetHttpClient()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
span, ctx := opentracing.StartSpanFromContext(ctx, "stackdriver query")
|
|
||||||
span.SetTag("target", query.Target)
|
|
||||||
span.SetTag("from", tsdbQuery.TimeRange.From)
|
|
||||||
span.SetTag("until", tsdbQuery.TimeRange.To)
|
|
||||||
span.SetTag("datasource_id", dsInfo.Id)
|
|
||||||
span.SetTag("org_id", dsInfo.OrgId)
|
|
||||||
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
opentracing.GlobalTracer().Inject(
|
|
||||||
span.Context(),
|
|
||||||
opentracing.HTTPHeaders,
|
|
||||||
opentracing.HTTPHeadersCarrier(req.Header))
|
|
||||||
|
|
||||||
res, err := ctxhttp.Do(ctx, httpClient, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := e.unmarshalResponse(res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
queryRes, err := e.parseResponse(data, query.RefID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -153,6 +119,53 @@ func (e *StackdriverExecutor) parseQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Stackd
|
|||||||
return stackdriverQueries, nil
|
return stackdriverQueries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *StackdriverQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, error) {
|
||||||
|
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID}
|
||||||
|
|
||||||
|
req, err := e.createRequest(ctx, e.dsInfo)
|
||||||
|
if err != nil {
|
||||||
|
queryResult.Error = err
|
||||||
|
return queryResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.RawQuery = query.Params.Encode()
|
||||||
|
queryResult.Meta.Set("rawQuery", req.URL.RawQuery)
|
||||||
|
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "stackdriver query")
|
||||||
|
span.SetTag("target", query.Target)
|
||||||
|
span.SetTag("from", tsdbQuery.TimeRange.From)
|
||||||
|
span.SetTag("until", tsdbQuery.TimeRange.To)
|
||||||
|
span.SetTag("datasource_id", e.dsInfo.Id)
|
||||||
|
span.SetTag("org_id", e.dsInfo.OrgId)
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
opentracing.GlobalTracer().Inject(
|
||||||
|
span.Context(),
|
||||||
|
opentracing.HTTPHeaders,
|
||||||
|
opentracing.HTTPHeadersCarrier(req.Header))
|
||||||
|
|
||||||
|
res, err := ctxhttp.Do(ctx, e.httpClient, req)
|
||||||
|
if err != nil {
|
||||||
|
queryResult.Error = err
|
||||||
|
return queryResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := e.unmarshalResponse(res)
|
||||||
|
if err != nil {
|
||||||
|
queryResult.Error = err
|
||||||
|
return queryResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = e.parseResponse(queryResult, data)
|
||||||
|
if err != nil {
|
||||||
|
queryResult.Error = err
|
||||||
|
return queryResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackDriverResponse, error) {
|
func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackDriverResponse, error) {
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
@ -161,24 +174,21 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode/100 != 2 {
|
if res.StatusCode/100 != 2 {
|
||||||
slog.Info("Request failed", "status", res.Status, "body", string(body))
|
slog.Error("Request failed", "status", res.Status, "body", string(body))
|
||||||
return StackDriverResponse{}, fmt.Errorf("Request failed status: %v", res.Status)
|
return StackDriverResponse{}, fmt.Errorf(string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var data StackDriverResponse
|
var data StackDriverResponse
|
||||||
err = json.Unmarshal(body, &data)
|
err = json.Unmarshal(body, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Info("Failed to unmarshal Stackdriver response", "error", err, "status", res.Status, "body", string(body))
|
slog.Error("Failed to unmarshal Stackdriver response", "error", err, "status", res.Status, "body", string(body))
|
||||||
return StackDriverResponse{}, err
|
return StackDriverResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *StackdriverExecutor) parseResponse(data StackDriverResponse, queryRefID string) (*tsdb.QueryResult, error) {
|
func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackDriverResponse) error {
|
||||||
queryRes := tsdb.NewQueryResult()
|
|
||||||
queryRes.RefId = queryRefID
|
|
||||||
|
|
||||||
for _, series := range data.TimeSeries {
|
for _, series := range data.TimeSeries {
|
||||||
points := make([]tsdb.TimePoint, 0)
|
points := make([]tsdb.TimePoint, 0)
|
||||||
for _, point := range series.Points {
|
for _, point := range series.Points {
|
||||||
@ -195,7 +205,7 @@ func (e *StackdriverExecutor) parseResponse(data StackDriverResponse, queryRefID
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryRes, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
|
func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
|
||||||
@ -227,7 +237,7 @@ func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.
|
|||||||
|
|
||||||
pluginproxy.ApplyRoute(ctx, req, proxyPass, stackdriverRoute, dsInfo)
|
pluginproxy.ApplyRoute(ctx, req, proxyPass, stackdriverRoute, dsInfo)
|
||||||
|
|
||||||
return req, err
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fixIntervalFormat(target string) string {
|
func fixIntervalFormat(target string) string {
|
||||||
|
@ -28,7 +28,7 @@ func TestStackdriver(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||||
"target": "target",
|
"target": "target",
|
||||||
"metricType": "time_series",
|
"metricType": "a/metric/type",
|
||||||
}),
|
}),
|
||||||
RefId: "A",
|
RefId: "A",
|
||||||
},
|
},
|
||||||
@ -44,52 +44,56 @@ func TestStackdriver(t *testing.T) {
|
|||||||
So(queries[0].Params["interval.startTime"][0], ShouldEqual, "2018-03-15T13:00:00Z")
|
So(queries[0].Params["interval.startTime"][0], ShouldEqual, "2018-03-15T13:00:00Z")
|
||||||
So(queries[0].Params["interval.endTime"][0], ShouldEqual, "2018-03-15T13:34:00Z")
|
So(queries[0].Params["interval.endTime"][0], ShouldEqual, "2018-03-15T13:34:00Z")
|
||||||
So(queries[0].Params["aggregation.perSeriesAligner"][0], ShouldEqual, "ALIGN_NONE")
|
So(queries[0].Params["aggregation.perSeriesAligner"][0], ShouldEqual, "ALIGN_NONE")
|
||||||
So(queries[0].Params["filter"][0], ShouldEqual, "time_series")
|
So(queries[0].Params["filter"][0], ShouldEqual, "a/metric/type")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Parse stackdriver response for data aggregated to one time series", func() {
|
Convey("Parse stackdriver response in the time series format", func() {
|
||||||
var data StackDriverResponse
|
Convey("when data from query aggregated to one time series", func() {
|
||||||
|
var data StackDriverResponse
|
||||||
|
|
||||||
jsonBody, err := ioutil.ReadFile("./test-data/1-series-response-agg-one-metric.json")
|
jsonBody, err := ioutil.ReadFile("./test-data/1-series-response-agg-one-metric.json")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
err = json.Unmarshal(jsonBody, &data)
|
err = json.Unmarshal(jsonBody, &data)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(data.TimeSeries), ShouldEqual, 1)
|
So(len(data.TimeSeries), ShouldEqual, 1)
|
||||||
|
|
||||||
res, err := executor.parseResponse(data, "A")
|
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
|
||||||
So(err, ShouldBeNil)
|
err = executor.parseResponse(res, data)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
So(len(res.Series), ShouldEqual, 1)
|
|
||||||
So(res.Series[0].Name, ShouldEqual, "serviceruntime.googleapis.com/api/request_count")
|
|
||||||
So(len(res.Series[0].Points), ShouldEqual, 3)
|
|
||||||
|
|
||||||
So(res.Series[0].Points[0][0].Float64, ShouldEqual, 1.0666666666667)
|
|
||||||
So(res.Series[0].Points[1][0].Float64, ShouldEqual, 1.05)
|
|
||||||
So(res.Series[0].Points[2][0].Float64, ShouldEqual, 0.05)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Parse stackdriver response for data with no aggregation", func() {
|
|
||||||
var data StackDriverResponse
|
|
||||||
|
|
||||||
jsonBody, err := ioutil.ReadFile("./test-data/2-series-response-no-agg.json")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
err = json.Unmarshal(jsonBody, &data)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(len(data.TimeSeries), ShouldEqual, 3)
|
|
||||||
|
|
||||||
res, err := executor.parseResponse(data, "A")
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
Convey("Should add labels to metric name", func() {
|
|
||||||
So(len(res.Series), ShouldEqual, 3)
|
|
||||||
So(res.Series[0].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-asia-east-1")
|
|
||||||
So(res.Series[1].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-europe-west-1")
|
|
||||||
So(res.Series[2].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-us-east-1")
|
|
||||||
|
|
||||||
|
So(len(res.Series), ShouldEqual, 1)
|
||||||
|
So(res.Series[0].Name, ShouldEqual, "serviceruntime.googleapis.com/api/request_count")
|
||||||
So(len(res.Series[0].Points), ShouldEqual, 3)
|
So(len(res.Series[0].Points), ShouldEqual, 3)
|
||||||
So(res.Series[0].Points[0][0].Float64, ShouldEqual, 9.7730520330369)
|
|
||||||
So(res.Series[0].Points[1][0].Float64, ShouldEqual, 9.7323568146676)
|
So(res.Series[0].Points[0][0].Float64, ShouldEqual, 1.0666666666667)
|
||||||
So(res.Series[0].Points[2][0].Float64, ShouldEqual, 9.8566497180145)
|
So(res.Series[0].Points[1][0].Float64, ShouldEqual, 1.05)
|
||||||
|
So(res.Series[0].Points[2][0].Float64, ShouldEqual, 0.05)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("when data from query with no aggregation", func() {
|
||||||
|
var data StackDriverResponse
|
||||||
|
|
||||||
|
jsonBody, err := ioutil.ReadFile("./test-data/2-series-response-no-agg.json")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
err = json.Unmarshal(jsonBody, &data)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(data.TimeSeries), ShouldEqual, 3)
|
||||||
|
|
||||||
|
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
|
||||||
|
err = executor.parseResponse(res, data)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should add labels to metric name", func() {
|
||||||
|
So(len(res.Series), ShouldEqual, 3)
|
||||||
|
So(res.Series[0].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-asia-east-1")
|
||||||
|
So(res.Series[1].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-europe-west-1")
|
||||||
|
So(res.Series[2].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-us-east-1")
|
||||||
|
|
||||||
|
So(len(res.Series[0].Points), ShouldEqual, 3)
|
||||||
|
So(res.Series[0].Points[0][0].Float64, ShouldEqual, 9.7730520330369)
|
||||||
|
So(res.Series[0].Points[1][0].Float64, ShouldEqual, 9.7323568146676)
|
||||||
|
So(res.Series[0].Points[2][0].Float64, ShouldEqual, 9.8566497180145)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -20,26 +20,27 @@ export default class StackdriverDatasource {
|
|||||||
|
|
||||||
const result = [];
|
const result = [];
|
||||||
|
|
||||||
try {
|
const { data } = await this.backendSrv.datasourceRequest({
|
||||||
const { data } = await this.backendSrv.datasourceRequest({
|
url: '/api/tsdb/query',
|
||||||
url: '/api/tsdb/query',
|
method: 'POST',
|
||||||
method: 'POST',
|
data: {
|
||||||
data: {
|
from: options.range.from.valueOf().toString(),
|
||||||
from: options.range.from.valueOf().toString(),
|
to: options.range.to.valueOf().toString(),
|
||||||
to: options.range.to.valueOf().toString(),
|
queries,
|
||||||
queries,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (data.results) {
|
if (data.results) {
|
||||||
Object['values'](data.results).forEach(queryRes => {
|
Object['values'](data.results).forEach(queryRes => {
|
||||||
queryRes.series.forEach(series => {
|
queryRes.series.forEach(series => {
|
||||||
result.push({ target: series.name, datapoints: series.points });
|
result.push({
|
||||||
|
target: series.name,
|
||||||
|
datapoints: series.points,
|
||||||
|
refId: queryRes.refId,
|
||||||
|
meta: queryRes.meta,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data: result };
|
return { data: result };
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
@ -2,15 +2,49 @@
|
|||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-9">Project</span>
|
<span class="gf-form-label width-9">Project</span>
|
||||||
<input class="gf-form-input" disabled type="text" ng-model='ctrl.target.project.name' get-options="ctrl.getProjects()" css-class="min-width-12"
|
<input class="gf-form-input" disabled type="text" ng-model='ctrl.target.project.name' get-options="ctrl.getProjects()"
|
||||||
/>
|
css-class="min-width-12" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-9">Metric Type</span>
|
<span class="gf-form-label width-9">Metric Type</span>
|
||||||
<gf-form-dropdown model="ctrl.target.metricType" get-options="ctrl.getMetricTypes($query)" class="gf-form-input" disabled
|
<gf-form-dropdown model="ctrl.target.metricType" get-options="ctrl.getMetricTypes($query)" class="min-width-20"
|
||||||
type="text" allow-custom="true" lookup-text="true" css-class="min-width-12" on-change="ctrl.refresh()"></gf-form-dropdown>
|
disabled type="text" allow-custom="true" lookup-text="true" css-class="min-width-12" on-change="ctrl.refresh()"></gf-form-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</query-editor-row>
|
<div class="gf-form-inline">
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp">
|
||||||
|
Show Help
|
||||||
|
<i class="fa fa-caret-down" ng-show="ctrl.showHelp"></i>
|
||||||
|
<i class="fa fa-caret-right" ng-hide="ctrl.showHelp"></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form" ng-show="ctrl.lastQueryMeta">
|
||||||
|
<label class="gf-form-label query-keyword" ng-click="ctrl.showLastQuery = !ctrl.showLastQuery">
|
||||||
|
Raw Query
|
||||||
|
<i class="fa fa-caret-down" ng-show="ctrl.showLastQuery"></i>
|
||||||
|
<i class="fa fa-caret-right" ng-hide="ctrl.showLastQuery"></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form" ng-show="ctrl.showLastQuery">
|
||||||
|
<pre class="gf-form-pre">{{ctrl.lastQueryMeta.rawQueryString}}</pre>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form" ng-show="ctrl.showHelp">
|
||||||
|
<pre class="gf-form-pre alert alert-info">
|
||||||
|
Help text for aliasing
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form" ng-show="ctrl.lastQueryError">
|
||||||
|
<pre class="gf-form-pre alert alert-error">{{ctrl.lastQueryError}}</pre>
|
||||||
|
</div>
|
||||||
|
</query-editor-row>
|
||||||
|
@ -3,15 +3,23 @@
|
|||||||
"type": "datasource",
|
"type": "datasource",
|
||||||
"id": "stackdriver",
|
"id": "stackdriver",
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
"alerting": false,
|
"alerting": true,
|
||||||
"annotations": false,
|
"annotations": false,
|
||||||
"queryOptions": {
|
"queryOptions": {
|
||||||
"maxDataPoints": true,
|
"maxDataPoints": true,
|
||||||
"cacheTimeout": true
|
"cacheTimeout": true
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"description": "Data Source for Stackdriver",
|
"description": "Google Stackdriver Datasource for Grafana",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0",
|
||||||
|
"logos": {
|
||||||
|
"small": "img/stackdriver_logo.png",
|
||||||
|
"large": "img/stackdriver_logo.png"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Grafana Project",
|
||||||
|
"url": "https://grafana.com"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,10 @@ import _ from 'lodash';
|
|||||||
import { QueryCtrl } from 'app/plugins/sdk';
|
import { QueryCtrl } from 'app/plugins/sdk';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
|
|
||||||
|
export interface QueryMeta {
|
||||||
|
rawQuery: string;
|
||||||
|
rawQueryString: string;
|
||||||
|
}
|
||||||
export class StackdriverQueryCtrl extends QueryCtrl {
|
export class StackdriverQueryCtrl extends QueryCtrl {
|
||||||
static templateUrl = 'partials/query.editor.html';
|
static templateUrl = 'partials/query.editor.html';
|
||||||
target: {
|
target: {
|
||||||
@ -10,21 +14,31 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
metricType: string;
|
metricType: string;
|
||||||
|
refId: string;
|
||||||
};
|
};
|
||||||
defaultDropdownValue = 'select';
|
defaultDropdownValue = 'Select metric';
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
project: {
|
project: {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
name: 'loading project...',
|
name: 'loading project...',
|
||||||
},
|
},
|
||||||
metricType: this.defaultDropdownValue,
|
// metricType: this.defaultDropdownValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
showHelp: boolean;
|
||||||
|
showLastQuery: boolean;
|
||||||
|
lastQueryMeta: QueryMeta;
|
||||||
|
lastQueryError?: string;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $injector) {
|
constructor($scope, $injector) {
|
||||||
super($scope, $injector);
|
super($scope, $injector);
|
||||||
_.defaultsDeep(this.target, this.defaults);
|
_.defaultsDeep(this.target, this.defaults);
|
||||||
|
|
||||||
|
this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope);
|
||||||
|
this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope);
|
||||||
|
|
||||||
this.getCurrentProject().then(this.getMetricTypes.bind(this));
|
this.getCurrentProject().then(this.getMetricTypes.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,4 +81,34 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDataReceived(dataList) {
|
||||||
|
this.lastQueryError = null;
|
||||||
|
this.lastQueryMeta = null;
|
||||||
|
|
||||||
|
const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId });
|
||||||
|
if (anySeriesFromQuery) {
|
||||||
|
this.lastQueryMeta = anySeriesFromQuery.meta;
|
||||||
|
this.lastQueryMeta.rawQueryString = decodeURIComponent(this.lastQueryMeta.rawQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDataError(err) {
|
||||||
|
if (err.data && err.data.results) {
|
||||||
|
const queryRes = err.data.results[this.target.refId];
|
||||||
|
if (queryRes) {
|
||||||
|
this.lastQueryMeta = queryRes.meta;
|
||||||
|
this.lastQueryMeta.rawQueryString = decodeURIComponent(this.lastQueryMeta.rawQuery);
|
||||||
|
|
||||||
|
let jsonBody;
|
||||||
|
try {
|
||||||
|
jsonBody = JSON.parse(queryRes.error);
|
||||||
|
} catch {
|
||||||
|
this.lastQueryError = queryRes.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastQueryError = jsonBody.error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user