Browse Source

stackdriver: add templating support for metric, filter and group by

Still have to figure out if we should have templating for aggregation
fields
Daniel Lee 7 years ago
parent
commit
8211f7d0ea

+ 18 - 0
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")
+				})
+			})
 		})
 	})
 }

+ 1 - 0
pkg/tsdb/stackdriver/types.go

@@ -10,6 +10,7 @@ type StackdriverQuery struct {
 	Params   url.Values
 	RefID    string
 	GroupBys []string
+	AliasBy  string
 }
 
 type StackdriverResponse struct {

+ 23 - 9
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);

+ 10 - 12
public/app/plugins/datasource/stackdriver/partials/query.editor.html

@@ -50,17 +50,6 @@
     </div>
   </div>
   <div class="gf-form-group" ng-if="ctrl.target.showAggregationOptions">
-    <div class="gf-form offset-width-9">
-      <label class="gf-form-label query-keyword width-12">Secondary Aggregation</label>
-      <div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
-        <select class="gf-form-input width-14" ng-model="ctrl.target.aggregation.secondaryCrossSeriesReducer" ng-options="f.value as f.text for f in ctrl.stackdriverConstants.aggOptions"
-          ng-change="ctrl.refresh()"></select>
-      </div>
-
-      <div class="gf-form gf-form--grow">
-        <div class="gf-form-label gf-form-label--grow"></div>
-      </div>
-    </div>
     <div class="gf-form offset-width-9">
       <label class="gf-form-label query-keyword width-12">Aligner</label>
       <div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
@@ -84,6 +73,15 @@
       </div>
     </div>
   </div>
+  <div class="gf-form-inline">
+    <div class="gf-form">
+      <span class="gf-form-label query-keyword width-9">Alias By</span>
+      <input type="text" class="gf-form-input width-12" ng-model="ctrl.target.aliasBy" />
+    </div>
+    <div class="gf-form gf-form--grow">
+      <div class="gf-form-label gf-form-label--grow"></div>
+    </div>
+  </div>
   <div class="gf-form-inline">
     <div class="gf-form">
       <span class="gf-form-label width-9">Project</span>
@@ -120,4 +118,4 @@ Help text for aliasing
   <div class="gf-form" ng-show="ctrl.lastQueryError">
     <pre class="gf-form-pre alert alert-error">{{ctrl.lastQueryError}}</pre>
   </div>
-</query-editor-row>
+</query-editor-row>

+ 9 - 5
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)) {

+ 44 - 6
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');
+      });
+    });
+  });
 });

+ 3 - 2
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: '',
   };
 }