mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
stackdriver: add templating support for metric, filter and group by
Still have to figure out if we should have templating for aggregation fields
This commit is contained in:
@@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ type StackdriverQuery struct {
|
||||
Params url.Values
|
||||
RefID string
|
||||
GroupBys []string
|
||||
AliasBy string
|
||||
}
|
||||
|
||||
type StackdriverResponse struct {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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: '',
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user