mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 21:12:37 +08:00
stackdriver: wip - filters for query editor
This commit is contained in:
@ -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>
|
||||
|
@ -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)];
|
||||
|
@ -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: [],
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user