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,23 +2,31 @@
<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>
<div class="gf-form">
<span class="gf-form-label query-keyword width-9">Group By</span>
<span class="gf-form-label query-keyword width-9">Group By</span>
<div class="gf-form" ng-repeat="segment in ctrl.groupBySegments">
<metric-segment segment="segment" get-options="ctrl.getGroupBys(segment, $index)" on-change="ctrl.groupByChanged(segment, $index)"></metric-segment>
</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">
@ -63,4 +71,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>

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,68 +8,147 @@ describe('StackdriverQueryCtrl', () => {
ctrl = createCtrlWithFakes();
});
describe('when labels are fetched', () => {
beforeEach(async () => {
ctrl.metricLabels = { 'metric-key-1': ['metric-value-1'] };
ctrl.resourceLabels = { 'resource-key-1': ['resource-value-1'] };
describe('group bys', () => {
describe('when labels are fetched', () => {
beforeEach(async () => {
ctrl.metricLabels = { 'metric-key-1': ['metric-value-1'] };
ctrl.resourceLabels = { 'resource-key-1': ['resource-value-1'] };
result = await ctrl.getGroupBys();
result = await ctrl.getGroupBys();
});
it('should populate group bys segments', () => {
expect(result.length).toBe(3);
expect(result[0].value).toBe('metric.label.metric-key-1');
expect(result[1].value).toBe('resource.label.resource-key-1');
expect(result[2].value).toBe('-- remove group by --');
});
});
it('should populate group bys segments', () => {
expect(result.length).toBe(3);
expect(result[0].value).toBe('metric.label.metric-key-1');
expect(result[1].value).toBe('resource.label.resource-key-1');
expect(result[2].value).toBe('-- remove group by --');
describe('when a group by label is selected', () => {
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.target.aggregation.groupBys = ['metric.label.metric-key-1', 'resource.label.resource-key-1'];
result = await ctrl.getGroupBys();
});
it('should not be used to populate group bys segments', () => {
expect(result.length).toBe(3);
expect(result[0].value).toBe('metric.label.metric-key-2');
expect(result[1].value).toBe('resource.label.resource-key-2');
expect(result[2].value).toBe('-- remove group by --');
});
});
describe('when a group by is selected', () => {
beforeEach(() => {
const removeSegment = { fake: true, value: '-- remove group by --' };
const segment = { value: 'groupby1' };
ctrl.groupBySegments = [segment, removeSegment];
ctrl.groupByChanged(segment);
});
it('should be added to group bys list', () => {
expect(ctrl.target.aggregation.groupBys.length).toBe(1);
});
});
describe('when a selected group by is removed', () => {
beforeEach(() => {
const removeSegment = { fake: true, value: '-- remove group by --' };
const segment = { value: 'groupby1' };
ctrl.groupBySegments = [segment, removeSegment];
ctrl.groupByChanged(removeSegment);
});
it('should be added to group bys list', () => {
expect(ctrl.target.aggregation.groupBys.length).toBe(0);
});
});
});
describe('when a group by label is selected', () => {
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.target.aggregation.groupBys = ['metric.label.metric-key-1', 'resource.label.resource-key-1'];
describe('filters', () => {
describe('when values for a condition filter part are fetched', () => {
beforeEach(async () => {
const segment = { type: 'condition' };
result = await ctrl.getFilters(segment, 0);
});
result = await ctrl.getGroupBys();
it('should populate group bys segments', () => {
expect(result.length).toBe(1);
expect(result[0].value).toBe('AND');
});
});
it('should not be used to populate group bys segments', () => {
expect(result.length).toBe(3);
expect(result[0].value).toBe('metric.label.metric-key-2');
expect(result[1].value).toBe('resource.label.resource-key-2');
expect(result[2].value).toBe('-- remove group by --');
});
});
describe('when values for a operator filter part are fetched', () => {
beforeEach(async () => {
const segment = { type: 'operator' };
result = await ctrl.getFilters(segment, 0);
});
describe('when a group by is selected', () => {
beforeEach(() => {
const removeSegment = { fake: true, value: '-- remove group by --' };
const segment = { value: 'groupby1' };
ctrl.groupBySegments = [segment, removeSegment];
ctrl.groupByChanged(segment);
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('!=~');
});
});
it('should be added to group bys list', () => {
expect(ctrl.target.aggregation.groupBys.length).toBe(1);
});
});
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'],
};
describe('when a selected group by is removed', () => {
beforeEach(() => {
const removeSegment = { fake: true, value: '-- remove group by --' };
const segment = { value: 'groupby1' };
ctrl.groupBySegments = [segment, removeSegment];
ctrl.groupByChanged(removeSegment);
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 --');
});
});
it('should be added to group bys list', () => {
expect(ctrl.target.aggregation.groupBys.length).toBe(0);
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');
});
});
});
});
@ -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: [],
};
}