diff --git a/pkg/tsdb/stackdriver/stackdriver_test.go b/pkg/tsdb/stackdriver/stackdriver_test.go index 8f1a7605e02..ef8e0e728d9 100644 --- a/pkg/tsdb/stackdriver/stackdriver_test.go +++ b/pkg/tsdb/stackdriver/stackdriver_test.go @@ -254,6 +254,24 @@ func TestStackdriver(t *testing.T) { So(res.Series[2].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-us-east-1 us-east1-b") }) }) + + Convey("when data from query with no aggregation and alias by", func() { + data, err := loadTestFile("./test-data/2-series-response-no-agg.json") + So(err, ShouldBeNil) + So(len(data.TimeSeries), ShouldEqual, 3) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &StackdriverQuery{AliasBy: "{{metric.label.instance_name}}", GroupBys: []string{"metric.label.instance_name", "resource.label.zone"}} + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + + Convey("Should use alias by formatting and only show instance name", func() { + So(len(res.Series), ShouldEqual, 3) + So(res.Series[0].Name, ShouldEqual, "collector-asia-east-1") + So(res.Series[1].Name, ShouldEqual, "collector-europe-west-1") + So(res.Series[2].Name, ShouldEqual, "collector-us-east-1") + }) + }) }) }) } diff --git a/pkg/tsdb/stackdriver/types.go b/pkg/tsdb/stackdriver/types.go index 88a2abd8548..ba2ed29a39c 100644 --- a/pkg/tsdb/stackdriver/types.go +++ b/pkg/tsdb/stackdriver/types.go @@ -10,6 +10,7 @@ type StackdriverQuery struct { Params url.Values RefID string GroupBys []string + AliasBy string } type StackdriverResponse struct { diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index 4d4dada3b92..ca078463e94 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -5,7 +5,7 @@ export default class StackdriverDatasource { baseUrl: string; projectName: string; - constructor(instanceSettings, private backendSrv) { + constructor(instanceSettings, private backendSrv, private templateSrv) { this.baseUrl = `/stackdriver/`; this.url = instanceSettings.url; this.doRequest = this.doRequest; @@ -22,21 +22,22 @@ export default class StackdriverDatasource { if (!t.hasOwnProperty('aggregation')) { t.aggregation = { crossSeriesReducer: 'REDUCE_MEAN', - secondaryCrossSeriesReducer: 'REDUCE_NONE', groupBys: [], }; } return { refId: t.refId, datasourceId: this.id, - metricType: t.metricType, - primaryAggregation: t.aggregation.crossSeriesReducer, - secondaryAggregation: t.aggregation.secondaryCrossSeriesReducer, - perSeriesAligner: t.aggregation.perSeriesAligner, - alignmentPeriod: t.aggregation.alignmentPeriod, - groupBys: t.aggregation.groupBys, + metricType: this.templateSrv.replace(t.metricType, options.scopedVars || {}), + primaryAggregation: this.templateSrv.replace(t.aggregation.crossSeriesReducer, options.scopedVars || {}), + perSeriesAligner: this.templateSrv.replace(t.aggregation.perSeriesAligner, options.scopedVars || {}), + alignmentPeriod: this.templateSrv.replace(t.aggregation.alignmentPeriod, options.scopedVars || {}), + groupBys: this.interpolateGroupBys(t.aggregation.groupBys, options.scopedVars), view: t.view || 'FULL', - filters: t.filters, + filters: (t.filters || []).map(f => { + return this.templateSrv.replace(f, options.scopedVars || {}); + }), + aliasBy: this.templateSrv.replace(t.aliasBy, options.scopedVars || {}), }; }); @@ -52,6 +53,19 @@ export default class StackdriverDatasource { return data; } + interpolateGroupBys(groupBys: string[], scopedVars): string[] { + let interpolatedGroupBys = []; + (groupBys || []).forEach(gb => { + const interpolated = this.templateSrv.replace(gb, scopedVars || {}, 'csv').split(','); + if (Array.isArray(interpolated)) { + interpolatedGroupBys = interpolatedGroupBys.concat(interpolated); + } else { + interpolatedGroupBys.push(interpolated); + } + }); + return interpolatedGroupBys; + } + async query(options) { const result = []; const data = await this.getTimeSeries(options); diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index e36e2b0b84d..1efe4388607 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -50,17 +50,6 @@
-
- -
- -
- -
-
-
-
@@ -84,6 +73,15 @@
+
+
+ Alias By + +
+
+
+
+
Project @@ -120,4 +118,4 @@ Help text for aliasing
{{ctrl.lastQueryError}}
- \ No newline at end of file + diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 2fdb145785e..3a5cd43f1c8 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -22,12 +22,12 @@ export class StackdriverQueryCtrl extends QueryCtrl { refId: string; aggregation: { crossSeriesReducer: string; - secondaryCrossSeriesReducer: string; alignmentPeriod: string; perSeriesAligner: string; groupBys: string[]; }; filters: string[]; + aliasBy: string; }; defaultDropdownValue = 'select metric'; defaultFilterValue = 'select value'; @@ -44,13 +44,13 @@ export class StackdriverQueryCtrl extends QueryCtrl { metricType: this.defaultDropdownValue, aggregation: { crossSeriesReducer: 'REDUCE_MEAN', - secondaryCrossSeriesReducer: 'REDUCE_NONE', alignmentPeriod: 'auto', perSeriesAligner: 'ALIGN_MEAN', groupBys: [], }, filters: [], showAggregationOptions: false, + aliasBy: '', }; groupBySegments: any[]; @@ -64,7 +64,7 @@ export class StackdriverQueryCtrl extends QueryCtrl { resourceLabels: { [key: string]: string[] }; /** @ngInject */ - constructor($scope, $injector, private uiSegmentSrv, private timeSrv) { + constructor($scope, $injector, private uiSegmentSrv, private timeSrv, private templateSrv) { super($scope, $injector); _.defaultsDeep(this.target, this.defaults); @@ -154,7 +154,7 @@ export class StackdriverQueryCtrl extends QueryCtrl { { refId: this.target.refId, datasourceId: this.datasource.id, - metricType: this.target.metricType, + metricType: this.templateSrv.replace(this.target.metricType), aggregation: { crossSeriesReducer: 'REDUCE_NONE', }, @@ -261,7 +261,11 @@ export class StackdriverQueryCtrl extends QueryCtrl { } if (segment.type === 'value') { - const filterKey = this.filterSegments[index - 2].value; + const filterKey = this.templateSrv.replace(this.filterSegments[index - 2].value); + if (!filterKey || !this.metricLabels || Object.keys(this.metricLabels).length === 0) { + return []; + } + const shortKey = filterKey.substring(filterKey.indexOf('.label.') + 7); if (filterKey.startsWith('metric.label.') && this.metricLabels.hasOwnProperty(shortKey)) { diff --git a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts index 7c5be8d89e3..5832ecf7304 100644 --- a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts @@ -1,6 +1,7 @@ import StackdriverDataSource from '../datasource'; import { metricDescriptors } from './testData'; import moment from 'moment'; +import { TemplateSrvStub } from 'test/specs/helpers'; describe('StackdriverDataSource', () => { const instanceSettings = { @@ -8,6 +9,8 @@ describe('StackdriverDataSource', () => { projectName: 'testproject', }, }; + const templateSrv = new TemplateSrvStub(); + describe('when performing testDataSource', () => { describe('and call to stackdriver api succeeds', () => { let ds; @@ -18,7 +21,7 @@ describe('StackdriverDataSource', () => { return Promise.resolve({ status: 200 }); }, }; - ds = new StackdriverDataSource(instanceSettings, backendSrv); + ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv); result = await ds.testDatasource(); }); it('should return successfully', () => { @@ -33,7 +36,7 @@ describe('StackdriverDataSource', () => { const backendSrv = { datasourceRequest: async () => Promise.resolve({ status: 200, data: metricDescriptors }), }; - ds = new StackdriverDataSource(instanceSettings, backendSrv); + ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv); result = await ds.testDatasource(); }); it('should return status success', () => { @@ -52,7 +55,7 @@ describe('StackdriverDataSource', () => { data: { error: { code: 400, message: 'Field interval.endTime had an invalid value' } }, }), }; - ds = new StackdriverDataSource(instanceSettings, backendSrv); + ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv); result = await ds.testDatasource(); }); @@ -88,7 +91,7 @@ describe('StackdriverDataSource', () => { return Promise.resolve({ status: 200, data: response }); }, }; - ds = new StackdriverDataSource(instanceSettings, backendSrv); + ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv); result = await ds.getProjects(); }); @@ -137,7 +140,7 @@ describe('StackdriverDataSource', () => { const backendSrv = { datasourceRequest: async () => Promise.resolve({ status: 200, data: response }), }; - ds = new StackdriverDataSource(instanceSettings, backendSrv); + ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv); }); it('should return a list of datapoints', () => { @@ -171,7 +174,7 @@ describe('StackdriverDataSource', () => { }); }, }; - ds = new StackdriverDataSource(instanceSettings, backendSrv); + ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv); result = await ds.getMetricTypes(); }); it('should return successfully', () => { @@ -180,4 +183,39 @@ describe('StackdriverDataSource', () => { expect(result[0].name).toBe('test metric name 1'); }); }); + + describe('when interpolating a template variable for group bys', () => { + let interpolated; + + describe('and is single value variable', () => { + beforeEach(() => { + templateSrv.data = { + test: 'groupby1', + }; + const ds = new StackdriverDataSource(instanceSettings, {}, templateSrv); + interpolated = ds.interpolateGroupBys(['[[test]]'], {}); + }); + + it('should replace the variable with the value', () => { + expect(interpolated.length).toBe(1); + expect(interpolated[0]).toBe('groupby1'); + }); + }); + + describe('and is multi value variable', () => { + beforeEach(() => { + templateSrv.data = { + test: 'groupby1,groupby2', + }; + const ds = new StackdriverDataSource(instanceSettings, {}, templateSrv); + interpolated = ds.interpolateGroupBys(['[[test]]'], {}); + }); + + it('should replace the variable with an array of group bys', () => { + expect(interpolated.length).toBe(2); + expect(interpolated[0]).toBe('groupby1'); + expect(interpolated[1]).toBe('groupby2'); + }); + }); + }); }); diff --git a/public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts b/public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts index cdd7a6ca617..dc4740fb6c3 100644 --- a/public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts @@ -1,4 +1,5 @@ import { StackdriverQueryCtrl } from '../query_ctrl'; +import { TemplateSrvStub } from 'test/specs/helpers'; describe('StackdriverQueryCtrl', () => { let ctrl; @@ -388,7 +389,7 @@ function createCtrlWithFakes(existingFilters?: string[]) { return { type: 'condition', value: val }; }, }; - return new StackdriverQueryCtrl(null, null, fakeSegmentServer, null); + return new StackdriverQueryCtrl(null, null, fakeSegmentServer, null, new TemplateSrvStub()); } function createTarget(existingFilters?: string[]) { @@ -401,11 +402,11 @@ function createTarget(existingFilters?: string[]) { refId: 'A', aggregation: { crossSeriesReducer: '', - secondaryCrossSeriesReducer: '', alignmentPeriod: '', perSeriesAligner: '', groupBys: [], }, filters: existingFilters || [], + aliasBy: '', }; }