stackdriver: wip - filters for query editor

This commit is contained in:
Daniel Lee
2018-09-14 01:49:39 +02:00
parent e2e95589e3
commit b5800ffea9
3 changed files with 215 additions and 66 deletions

View File

@ -2,18 +2,26 @@
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Metric Type</span>
<gf-form-dropdown model="ctrl.target.metricType" get-options="ctrl.getMetricTypes($query)" class="min-width-20" disabled
type="text" allow-custom="true" lookup-text="true" css-class="min-width-12" on-change="ctrl.onMetricTypeChange()"></gf-form-dropdown>
<gf-form-dropdown model="ctrl.target.metricType" get-options="ctrl.getMetricTypes($query)" class="min-width-20"
disabled type="text" allow-custom="true" lookup-text="true" css-class="min-width-12" on-change="ctrl.onMetricTypeChange()"></gf-form-dropdown>
</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 query-keyword width-9">Filter</span>
<div class="gf-form" ng-repeat="segment in ctrl.filterSegments">
<metric-segment segment="segment" get-options="ctrl.getFilters(segment, $index)" on-change="ctrl.filterSegmentUpdated(segment, $index)"></metric-segment>
</div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Aggregation</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input width-11" ng-model="ctrl.target.aggregation.crossSeriesReducer" ng-options="f.value as f.text for f in ctrl.aggOptions"
<select class="gf-form-input width-14" ng-model="ctrl.target.aggregation.crossSeriesReducer" ng-options="f.value as f.text for f in ctrl.aggOptions"
ng-change="ctrl.refresh()"></select>
</div>
</div>
@ -30,8 +38,8 @@
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Project</span>
<input class="gf-form-input" disabled type="text" ng-model='ctrl.target.project.name' get-options="ctrl.getProjects()" css-class="min-width-12"
/>
<input class="gf-form-input" disabled type="text" ng-model='ctrl.target.project.name' get-options="ctrl.getProjects()"
css-class="min-width-12" />
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp">

View File

@ -2,17 +2,18 @@ import _ from 'lodash';
import { QueryCtrl } from 'app/plugins/sdk';
import appEvents from 'app/core/app_events';
export interface LabelType {
key: string;
value: string;
}
export interface QueryMeta {
rawQuery: string;
rawQueryString: string;
metricLabels: { [key: string]: string[] };
resourceLabels: { [key: string]: string[] };
}
export interface Filter {
key: string;
operator: string;
value: string;
}
export class StackdriverQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html';
target: {
@ -28,6 +29,7 @@ export class StackdriverQueryCtrl extends QueryCtrl {
perSeriesAligner: string;
groupBys: string[];
};
filters: Filter[];
};
defaultDropdownValue = 'Select metric';
@ -43,9 +45,12 @@ export class StackdriverQueryCtrl extends QueryCtrl {
perSeriesAligner: '',
groupBys: [],
},
filters: [],
};
groupBySegments: any[];
filterSegments: any[];
removeSegment: any;
aggOptions = [
{ text: 'none', value: 'REDUCE_NONE' },
@ -65,9 +70,8 @@ export class StackdriverQueryCtrl extends QueryCtrl {
showLastQuery: boolean;
lastQueryMeta: QueryMeta;
lastQueryError?: string;
metricLabels: LabelType[];
resourceLabels: LabelType[];
removeSegment: any;
metricLabels: { [key: string]: string[] };
resourceLabels: { [key: string]: string[] };
/** @ngInject */
constructor($scope, $injector, private uiSegmentSrv, private timeSrv) {
@ -81,11 +85,23 @@ export class StackdriverQueryCtrl extends QueryCtrl {
.then(this.getMetricTypes.bind(this))
.then(this.getLabels.bind(this));
this.initSegments();
}
initSegments() {
this.groupBySegments = this.target.aggregation.groupBys.map(groupBy => {
return uiSegmentSrv.getSegmentForValue(groupBy);
return this.uiSegmentSrv.getSegmentForValue(groupBy);
});
this.removeSegment = uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' });
this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' });
this.ensurePlusButton(this.groupBySegments);
this.filterSegments = [];
this.target.filters.forEach(f => {
this.filterSegments.push(this.uiSegmentSrv.newKey(f.key));
this.filterSegments.push(this.uiSegmentSrv.newOperator(f.operator));
this.filterSegments.push(this.uiSegmentSrv.newKeyValue(f.value));
});
this.ensurePlusButton(this.filterSegments);
}
async getCurrentProject() {
@ -153,7 +169,7 @@ export class StackdriverQueryCtrl extends QueryCtrl {
this.getLabels();
}
getGroupBys() {
getGroupBys(removeText?: string) {
const metricLabels = Object.keys(this.metricLabels)
.filter(ml => {
return this.target.aggregation.groupBys.indexOf('metric.label.' + ml) === -1;
@ -176,6 +192,7 @@ export class StackdriverQueryCtrl extends QueryCtrl {
});
});
this.removeSegment.value = removeText || '-- remove group by --';
return Promise.resolve([...metricLabels, ...resourceLabels, this.removeSegment]);
}
@ -198,6 +215,45 @@ export class StackdriverQueryCtrl extends QueryCtrl {
this.refresh();
}
getFilters(segment, index) {
if (segment.type === 'condition') {
return [this.uiSegmentSrv.newSegment('AND')];
}
if (segment.type === 'operator') {
return this.uiSegmentSrv.newOperators(['=', '!=', '=~', '!=~']);
}
if (segment.type === 'key' || segment.type === 'plus-button') {
return this.getGroupBys('-- remove filter --');
}
if (segment.type === 'value') {
const filterKey = this.filterSegments[index - 2].value;
if (this.metricLabels[filterKey]) {
return this.getValuesForFilterKey(this.metricLabels[filterKey]);
}
if (this.resourceLabels[filterKey]) {
return this.getValuesForFilterKey(this.resourceLabels[filterKey]);
}
}
return [];
}
getValuesForFilterKey(labels: any[]) {
const filterValues = labels.map(l => {
return this.uiSegmentSrv.newSegment({
value: `${l}`,
expandable: false,
});
});
return filterValues;
}
ensurePlusButton(segments) {
const count = segments.length;
const lastSegment = segments[Math.max(count - 1, 0)];

View File

@ -8,6 +8,7 @@ describe('StackdriverQueryCtrl', () => {
ctrl = createCtrlWithFakes();
});
describe('group bys', () => {
describe('when labels are fetched', () => {
beforeEach(async () => {
ctrl.metricLabels = { 'metric-key-1': ['metric-value-1'] };
@ -74,6 +75,84 @@ describe('StackdriverQueryCtrl', () => {
});
});
describe('filters', () => {
describe('when values for a condition filter part are fetched', () => {
beforeEach(async () => {
const segment = { type: 'condition' };
result = await ctrl.getFilters(segment, 0);
});
it('should populate group bys segments', () => {
expect(result.length).toBe(1);
expect(result[0].value).toBe('AND');
});
});
describe('when values for a operator filter part are fetched', () => {
beforeEach(async () => {
const segment = { type: 'operator' };
result = await ctrl.getFilters(segment, 0);
});
it('should populate group bys segments', () => {
expect(result.length).toBe(4);
expect(result[0].value).toBe('=');
expect(result[1].value).toBe('!=');
expect(result[2].value).toBe('=~');
expect(result[3].value).toBe('!=~');
});
});
describe('when values for a key filter part are fetched', () => {
beforeEach(async () => {
ctrl.metricLabels = {
'metric-key-1': ['metric-value-1'],
'metric-key-2': ['metric-value-2'],
};
ctrl.resourceLabels = {
'resource-key-1': ['resource-value-1'],
'resource-key-2': ['resource-value-2'],
};
const segment = { type: 'key' };
result = await ctrl.getFilters(segment, 0);
});
it('should populate group bys segments', () => {
expect(result.length).toBe(5);
expect(result[0].value).toBe('metric.label.metric-key-1');
expect(result[1].value).toBe('metric.label.metric-key-2');
expect(result[2].value).toBe('resource.label.resource-key-1');
expect(result[3].value).toBe('resource.label.resource-key-2');
expect(result[4].value).toBe('-- remove filter --');
});
});
describe('when values for a value filter part are fetched', () => {
beforeEach(async () => {
ctrl.metricLabels = {
'metric-key-1': ['metric-value-1'],
'metric-key-2': ['metric-value-2'],
};
ctrl.resourceLabels = {
'resource-key-1': ['resource-value-1'],
'resource-key-2': ['resource-value-2'],
};
ctrl.filterSegments = [{ type: 'key', value: 'metric-key-1' }, { type: 'operator', value: '=' }];
const segment = { type: 'value' };
result = await ctrl.getFilters(segment, 2);
});
it('should populate group bys segments', () => {
expect(result.length).toBe(1);
expect(result[0].value).toBe('metric-value-1');
});
});
});
});
function createCtrlWithFakes() {
StackdriverQueryCtrl.prototype.panelCtrl = {
events: { on: () => {} },
@ -90,7 +169,12 @@ function createCtrlWithFakes() {
const fakeSegmentServer = {
newSegment: obj => {
return { value: obj.value };
return { value: obj.value ? obj.value : obj };
},
newOperators: ops => {
return ops.map(o => {
return { type: 'operator', value: o, text: o };
});
},
newPlusButton: () => {},
};
@ -111,5 +195,6 @@ function createTarget() {
perSeriesAligner: '',
groupBys: [],
},
filters: [],
};
}