mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 15:42:13 +08:00
stackdriver: wip - filters for query editor
This commit is contained in:
@ -2,18 +2,26 @@
|
|||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-9">Metric Type</span>
|
<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
|
<gf-form-dropdown model="ctrl.target.metricType" get-options="ctrl.getMetricTypes($query)" class="min-width-20"
|
||||||
type="text" allow-custom="true" lookup-text="true" css-class="min-width-12" on-change="ctrl.onMetricTypeChange()"></gf-form-dropdown>
|
disabled type="text" allow-custom="true" lookup-text="true" css-class="min-width-12" on-change="ctrl.onMetricTypeChange()"></gf-form-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form gf-form--grow">
|
<div class="gf-form gf-form--grow">
|
||||||
<div class="gf-form-label gf-form-label--grow"></div>
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
</div>
|
</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-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword width-9">Aggregation</label>
|
<label class="gf-form-label query-keyword width-9">Aggregation</label>
|
||||||
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
|
<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>
|
ng-change="ctrl.refresh()"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -30,8 +38,8 @@
|
|||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-9">Project</span>
|
<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>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp">
|
<label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp">
|
||||||
|
@ -2,17 +2,18 @@ import _ from 'lodash';
|
|||||||
import { QueryCtrl } from 'app/plugins/sdk';
|
import { QueryCtrl } from 'app/plugins/sdk';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
|
|
||||||
export interface LabelType {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QueryMeta {
|
export interface QueryMeta {
|
||||||
rawQuery: string;
|
rawQuery: string;
|
||||||
rawQueryString: string;
|
rawQueryString: string;
|
||||||
metricLabels: { [key: string]: string[] };
|
metricLabels: { [key: string]: string[] };
|
||||||
resourceLabels: { [key: string]: string[] };
|
resourceLabels: { [key: string]: string[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Filter {
|
||||||
|
key: string;
|
||||||
|
operator: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
export class StackdriverQueryCtrl extends QueryCtrl {
|
export class StackdriverQueryCtrl extends QueryCtrl {
|
||||||
static templateUrl = 'partials/query.editor.html';
|
static templateUrl = 'partials/query.editor.html';
|
||||||
target: {
|
target: {
|
||||||
@ -28,6 +29,7 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
perSeriesAligner: string;
|
perSeriesAligner: string;
|
||||||
groupBys: string[];
|
groupBys: string[];
|
||||||
};
|
};
|
||||||
|
filters: Filter[];
|
||||||
};
|
};
|
||||||
defaultDropdownValue = 'Select metric';
|
defaultDropdownValue = 'Select metric';
|
||||||
|
|
||||||
@ -43,9 +45,12 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
perSeriesAligner: '',
|
perSeriesAligner: '',
|
||||||
groupBys: [],
|
groupBys: [],
|
||||||
},
|
},
|
||||||
|
filters: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
groupBySegments: any[];
|
groupBySegments: any[];
|
||||||
|
filterSegments: any[];
|
||||||
|
removeSegment: any;
|
||||||
|
|
||||||
aggOptions = [
|
aggOptions = [
|
||||||
{ text: 'none', value: 'REDUCE_NONE' },
|
{ text: 'none', value: 'REDUCE_NONE' },
|
||||||
@ -65,9 +70,8 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
showLastQuery: boolean;
|
showLastQuery: boolean;
|
||||||
lastQueryMeta: QueryMeta;
|
lastQueryMeta: QueryMeta;
|
||||||
lastQueryError?: string;
|
lastQueryError?: string;
|
||||||
metricLabels: LabelType[];
|
metricLabels: { [key: string]: string[] };
|
||||||
resourceLabels: LabelType[];
|
resourceLabels: { [key: string]: string[] };
|
||||||
removeSegment: any;
|
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $injector, private uiSegmentSrv, private timeSrv) {
|
constructor($scope, $injector, private uiSegmentSrv, private timeSrv) {
|
||||||
@ -81,11 +85,23 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
.then(this.getMetricTypes.bind(this))
|
.then(this.getMetricTypes.bind(this))
|
||||||
.then(this.getLabels.bind(this));
|
.then(this.getLabels.bind(this));
|
||||||
|
|
||||||
|
this.initSegments();
|
||||||
|
}
|
||||||
|
|
||||||
|
initSegments() {
|
||||||
this.groupBySegments = this.target.aggregation.groupBys.map(groupBy => {
|
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.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() {
|
async getCurrentProject() {
|
||||||
@ -153,7 +169,7 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
this.getLabels();
|
this.getLabels();
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroupBys() {
|
getGroupBys(removeText?: string) {
|
||||||
const metricLabels = Object.keys(this.metricLabels)
|
const metricLabels = Object.keys(this.metricLabels)
|
||||||
.filter(ml => {
|
.filter(ml => {
|
||||||
return this.target.aggregation.groupBys.indexOf('metric.label.' + ml) === -1;
|
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]);
|
return Promise.resolve([...metricLabels, ...resourceLabels, this.removeSegment]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,6 +215,45 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
this.refresh();
|
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) {
|
ensurePlusButton(segments) {
|
||||||
const count = segments.length;
|
const count = segments.length;
|
||||||
const lastSegment = segments[Math.max(count - 1, 0)];
|
const lastSegment = segments[Math.max(count - 1, 0)];
|
||||||
|
@ -8,6 +8,7 @@ describe('StackdriverQueryCtrl', () => {
|
|||||||
ctrl = createCtrlWithFakes();
|
ctrl = createCtrlWithFakes();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('group bys', () => {
|
||||||
describe('when labels are fetched', () => {
|
describe('when labels are fetched', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ctrl.metricLabels = { 'metric-key-1': ['metric-value-1'] };
|
ctrl.metricLabels = { 'metric-key-1': ['metric-value-1'] };
|
||||||
@ -72,6 +73,84 @@ describe('StackdriverQueryCtrl', () => {
|
|||||||
expect(ctrl.target.aggregation.groupBys.length).toBe(0);
|
expect(ctrl.target.aggregation.groupBys.length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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() {
|
function createCtrlWithFakes() {
|
||||||
@ -90,7 +169,12 @@ function createCtrlWithFakes() {
|
|||||||
|
|
||||||
const fakeSegmentServer = {
|
const fakeSegmentServer = {
|
||||||
newSegment: obj => {
|
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: () => {},
|
newPlusButton: () => {},
|
||||||
};
|
};
|
||||||
@ -111,5 +195,6 @@ function createTarget() {
|
|||||||
perSeriesAligner: '',
|
perSeriesAligner: '',
|
||||||
groupBys: [],
|
groupBys: [],
|
||||||
},
|
},
|
||||||
|
filters: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user