diff --git a/pkg/tsdb/stackdriver/stackdriver.go b/pkg/tsdb/stackdriver/stackdriver.go index 8fa8178372c..728b934108c 100644 --- a/pkg/tsdb/stackdriver/stackdriver.go +++ b/pkg/tsdb/stackdriver/stackdriver.go @@ -133,6 +133,12 @@ func setAggParams(params *url.Values, query *tsdb.Query) { params.Add("aggregation.alignmentPeriod", "+60s") } + groupBys := query.Model.Get("groupBys").MustArray() + if len(groupBys) > 0 { + for i := 0; i < len(groupBys); i++ { + params.Add("aggregation.groupByFields", groupBys[i].(string)) + } + } } func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *StackdriverQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, error) { @@ -205,6 +211,9 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackDriver } func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackDriverResponse) error { + metricLabels := make(map[string][]string) + // resourceLabels := make(map[string][]string) + for _, series := range data.TimeSeries { points := make([]tsdb.TimePoint, 0) @@ -215,15 +224,21 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta } metricName := series.Metric.Type - for _, value := range series.Metric.Labels { + for key, value := range series.Metric.Labels { + metricLabels[key] = append(metricLabels[key], value) metricName += " " + value } + + // queryRes.Meta.Set("resourceLabels", series.Resource.Labels) + queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ Name: metricName, Points: points, }) } + queryRes.Meta.Set("metricLabels", metricLabels) + return nil } diff --git a/pkg/tsdb/stackdriver/stackdriver_test.go b/pkg/tsdb/stackdriver/stackdriver_test.go index 9e5b3a04df1..36e96d5d20d 100644 --- a/pkg/tsdb/stackdriver/stackdriver_test.go +++ b/pkg/tsdb/stackdriver/stackdriver_test.go @@ -71,6 +71,29 @@ func TestStackdriver(t *testing.T) { So(queries[0].Params["filter"][0], ShouldEqual, "metric.type=\"a/metric/type\"") }) + Convey("and query has group bys", func() { + tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{ + "target": "target", + "metricType": "a/metric/type", + "primaryAggregation": "REDUCE_NONE", + "groupBys": []interface{}{"metric.label.group1", "metric.label.group2"}, + }) + + 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, 5) + 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["aggregation.groupByFields"][0], ShouldEqual, "metric.label.group1") + So(queries[0].Params["aggregation.groupByFields"][1], ShouldEqual, "metric.label.group2") + So(queries[0].Params["filter"][0], ShouldEqual, "metric.type=\"a/metric/type\"") + }) + }) Convey("Parse stackdriver response in the time series format", func() { @@ -127,6 +150,11 @@ func TestStackdriver(t *testing.T) { So(res.Series[0].Points[1][0].Float64, ShouldEqual, 9.7323568146676) So(res.Series[0].Points[2][0].Float64, ShouldEqual, 9.7730520330369) }) + + Convey("Should add meta for labels to the response", func() { + instanceName := res.Meta.Get("metricLabels").MustMap()["instance_name"] + So(instanceName, ShouldNotBeNil) + }) }) }) }) diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index 5232144332f..2ad4faecad5 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -16,7 +16,8 @@ export default class StackdriverDatasource { refId: t.refId, datasourceId: this.id, metricType: t.metricType, - primaryAggregation: t.aggregation, + primaryAggregation: t.aggregation.crossSeriesReducer, + groupBys: t.aggregation.groupBys, })); const result = []; diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 36331d439c6..dbcc10e9ed3 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -10,13 +10,18 @@
-
+
-
-
+
+
+ Group By +
+ +
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 21a9988a9d7..92b431c960b 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -5,6 +5,7 @@ import appEvents from 'app/core/app_events'; export interface QueryMeta { rawQuery: string; rawQueryString: string; + metricLabels: any; } export class StackdriverQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; @@ -15,6 +16,12 @@ export class StackdriverQueryCtrl extends QueryCtrl { }; metricType: string; refId: string; + aggregation: { + crossSeriesReducer: string; + alignmentPeriod: string; + perSeriesAligner: string; + groupBys: string[]; + }; }; defaultDropdownValue = 'Select metric'; @@ -24,9 +31,16 @@ export class StackdriverQueryCtrl extends QueryCtrl { name: 'loading project...', }, metricType: this.defaultDropdownValue, - aggregation: 'REDUCE_MEAN', + aggregation: { + crossSeriesReducer: 'REDUCE_MEAN', + alignmentPeriod: '', + perSeriesAligner: '', + groupBys: [], + }, }; + groupBySegments: any[]; + aggOptions = [ { text: 'none', value: 'REDUCE_NONE' }, { text: 'mean', value: 'REDUCE_MEAN' }, @@ -47,7 +61,7 @@ export class StackdriverQueryCtrl extends QueryCtrl { lastQueryError?: string; /** @ngInject */ - constructor($scope, $injector) { + constructor($scope, $injector, private uiSegmentSrv) { super($scope, $injector); _.defaultsDeep(this.target, this.defaults); @@ -55,6 +69,11 @@ export class StackdriverQueryCtrl extends QueryCtrl { this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); this.getCurrentProject().then(this.getMetricTypes.bind(this)); + + this.groupBySegments = _.map(this.target.aggregation.groupBys, groupBy => { + return uiSegmentSrv.getSegmentForValue(groupBy); + }); + this.ensurePlusButton(this.groupBySegments); } async getCurrentProject() { @@ -97,6 +116,37 @@ export class StackdriverQueryCtrl extends QueryCtrl { } } + getGroupBys() { + const segments = _.map(Object.keys(this.lastQueryMeta.metricLabels), (label: string) => { + return this.uiSegmentSrv.newSegment({ value: label, expandable: false }); + }); + + return Promise.resolve(segments); + } + + groupByChanged(segment, index) { + this.target.aggregation.groupBys = _.reduce( + this.groupBySegments, + function(memo, seg) { + if (!seg.fake) { + memo.push(seg.value); + } + return memo; + }, + [] + ); + this.ensurePlusButton(this.groupBySegments); + } + + ensurePlusButton(segments) { + const count = segments.length; + const lastSegment = segments[Math.max(count - 1, 0)]; + + if (!lastSegment || lastSegment.type !== 'plus-button') { + segments.push(this.uiSegmentSrv.newPlusButton()); + } + } + onDataReceived(dataList) { this.lastQueryError = null; this.lastQueryMeta = null;