diff --git a/pkg/tsdb/stackdriver/stackdriver.go b/pkg/tsdb/stackdriver/stackdriver.go index 2f539e21064..ceb1f02a222 100644 --- a/pkg/tsdb/stackdriver/stackdriver.go +++ b/pkg/tsdb/stackdriver/stackdriver.go @@ -59,7 +59,7 @@ func (e *StackdriverExecutor) Query(ctx context.Context, dsInfo *models.DataSour Results: make(map[string]*tsdb.QueryResult), } - queries, err := e.parseQueries(tsdbQuery) + queries, err := e.buildQueries(tsdbQuery) if err != nil { return nil, err } @@ -75,7 +75,7 @@ func (e *StackdriverExecutor) Query(ctx context.Context, dsInfo *models.DataSour return result, nil } -func (e *StackdriverExecutor) parseQueries(tsdbQuery *tsdb.TsdbQuery) ([]*StackdriverQuery, error) { +func (e *StackdriverExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*StackdriverQuery, error) { stackdriverQueries := []*StackdriverQuery{} startTime, err := tsdbQuery.TimeRange.ParseFrom() @@ -102,8 +102,8 @@ func (e *StackdriverExecutor) parseQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Stackd params := url.Values{} params.Add("interval.startTime", startTime.UTC().Format(time.RFC3339)) params.Add("interval.endTime", endTime.UTC().Format(time.RFC3339)) - params.Add("aggregation.perSeriesAligner", "ALIGN_NONE") - params.Add("filter", metricType) + params.Add("filter", "metric.type=\""+metricType+"\"") + setAggParams(¶ms, query) if setting.Env == setting.DEV { slog.Debug("Stackdriver request", "params", params) @@ -119,6 +119,22 @@ func (e *StackdriverExecutor) parseQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Stackd return stackdriverQueries, nil } +func setAggParams(params *url.Values, query *tsdb.Query) { + primaryAggregation := query.Model.Get("primaryAggregation").MustString() + if primaryAggregation == "" { + primaryAggregation = "REDUCE_NONE" + } + + if primaryAggregation == "REDUCE_NONE" { + params.Add("aggregation.perSeriesAligner", "ALIGN_NONE") + } else { + params.Add("aggregation.crossSeriesReducer", primaryAggregation) + params.Add("aggregation.perSeriesAligner", "ALIGN_MEAN") + params.Add("aggregation.alignmentPeriod", "+60s") + } + +} + func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *StackdriverQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, error) { queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID} diff --git a/pkg/tsdb/stackdriver/stackdriver_test.go b/pkg/tsdb/stackdriver/stackdriver_test.go index 228f0a48537..01d55e6e1b1 100644 --- a/pkg/tsdb/stackdriver/stackdriver_test.go +++ b/pkg/tsdb/stackdriver/stackdriver_test.go @@ -17,7 +17,7 @@ func TestStackdriver(t *testing.T) { Convey("Stackdriver", t, func() { executor := &StackdriverExecutor{} - Convey("Parse query from frontend", func() { + Convey("Parse queries from frontend and build Stackdriver API queries", func() { fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local) tsdbQuery := &tsdb.TsdbQuery{ TimeRange: &tsdb.TimeRange{ @@ -34,17 +34,43 @@ func TestStackdriver(t *testing.T) { }, }, } - queries, err := executor.parseQueries(tsdbQuery) - So(err, ShouldBeNil) - So(len(queries), ShouldEqual, 1) - So(queries[0].RefID, ShouldEqual, "A") - So(queries[0].Target, ShouldEqual, "target") - So(len(queries[0].Params), ShouldEqual, 4) - 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["aggregation.perSeriesAligner"][0], ShouldEqual, "ALIGN_NONE") - So(queries[0].Params["filter"][0], ShouldEqual, "a/metric/type") + Convey("and query has no aggregation set", func() { + queries, err := executor.buildQueries(tsdbQuery) + So(err, ShouldBeNil) + + So(len(queries), ShouldEqual, 1) + So(queries[0].RefID, ShouldEqual, "A") + So(queries[0].Target, ShouldEqual, "target") + So(len(queries[0].Params), ShouldEqual, 4) + 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["aggregation.perSeriesAligner"][0], ShouldEqual, "ALIGN_NONE") + So(queries[0].Params["filter"][0], ShouldEqual, "metric.type=\"a/metric/type\"") + }) + + Convey("and query has aggregation mean set", func() { + tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{ + "target": "target", + "metricType": "a/metric/type", + "primaryAggregation": "REDUCE_MEAN", + }) + + queries, err := executor.buildQueries(tsdbQuery) + So(err, ShouldBeNil) + + So(len(queries), ShouldEqual, 1) + So(queries[0].RefID, ShouldEqual, "A") + So(queries[0].Target, ShouldEqual, "target") + So(len(queries[0].Params), ShouldEqual, 6) + 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["aggregation.crossSeriesReducer"][0], ShouldEqual, "REDUCE_MEAN") + So(queries[0].Params["aggregation.perSeriesAligner"][0], ShouldEqual, "ALIGN_MEAN") + So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, "+60s") + So(queries[0].Params["filter"][0], ShouldEqual, "metric.type=\"a/metric/type\"") + }) + }) Convey("Parse stackdriver response in the time series format", func() { diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index 231e21e8ec5..5232144332f 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -15,7 +15,8 @@ export default class StackdriverDatasource { const queries = options.targets.filter(target => !target.hide).map(t => ({ refId: t.refId, datasourceId: this.id, - metricType: `metric.type="${t.metricType}"`, + metricType: t.metricType, + primaryAggregation: t.aggregation, })); const result = []; @@ -32,6 +33,9 @@ export default class StackdriverDatasource { if (data.results) { Object['values'](data.results).forEach(queryRes => { + if (!queryRes.series) { + return; + } queryRes.series.forEach(series => { result.push({ target: series.name, diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 021b1f63212..36331d439c6 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -1,11 +1,4 @@ -
-
- Project - -
-
Metric Type @@ -17,6 +10,24 @@
+
+ +
+ +
+
+
+
+
+
+
+
+
+ Project + +
-
{{ctrl.lastQueryError}}
-
+
{{ctrl.lastQueryError}}
+
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index a018bd4445e..21a9988a9d7 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -23,9 +23,24 @@ export class StackdriverQueryCtrl extends QueryCtrl { id: 'default', name: 'loading project...', }, - // metricType: this.defaultDropdownValue, + metricType: this.defaultDropdownValue, + aggregation: 'REDUCE_MEAN', }; + aggOptions = [ + { text: 'none', value: 'REDUCE_NONE' }, + { text: 'mean', value: 'REDUCE_MEAN' }, + { text: 'min', value: 'REDUCE_MIN' }, + { text: 'max', value: 'REDUCE_MAX' }, + { text: 'sum', value: 'REDUCE_SUM' }, + { text: 'std. dev.', value: 'REDUCE_STDDEV' }, + { text: 'count', value: 'REDUCE_COUNT' }, + { text: '99th percentile', value: 'REDUCE_PERCENTILE_99' }, + { text: '95th percentile', value: 'REDUCE_PERCENTILE_95' }, + { text: '50th percentile', value: 'REDUCE_PERCENTILE_50' }, + { text: '5th percentile', value: 'REDUCE_PERCENTILE_05' }, + ]; + showHelp: boolean; showLastQuery: boolean; lastQueryMeta: QueryMeta; diff --git a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts index 1fb06f7c65a..6983e41a21f 100644 --- a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts @@ -1,5 +1,6 @@ import StackdriverDataSource from '../datasource'; import { metricDescriptors } from './testData'; +import moment from 'moment'; describe('StackdriverDataSource', () => { describe('when performing testDataSource', () => { @@ -93,4 +94,51 @@ describe('StackdriverDataSource', () => { }); }); }); + + describe('When performing query', () => { + const options = { + range: { + from: moment.utc('2017-08-22T20:00:00Z'), + to: moment.utc('2017-08-22T23:59:00Z'), + }, + rangeRaw: { + from: 'now-4h', + to: 'now', + }, + targets: [ + { + refId: 'A', + }, + ], + }; + + describe('and no time series data is returned', () => { + let ds; + const response = { + results: { + A: { + refId: 'A', + meta: { + rawQuery: 'arawquerystring', + }, + series: null, + tables: null, + }, + }, + }; + + beforeEach(() => { + const backendSrv = { + datasourceRequest: async () => Promise.resolve({ status: 200, data: response }), + }; + ds = new StackdriverDataSource({}, backendSrv); + }); + + it('should return a list of datapoints', () => { + return ds.query(options).then(results => { + expect(results.data.length).toBe(0); + }); + }); + }); + }); });