stackdriver: refactoring - extract out filtersegments component

This commit is contained in:
Daniel Lee
2018-09-24 00:59:28 +02:00
parent 681cd7496e
commit 636d8421d0
4 changed files with 218 additions and 156 deletions
@@ -0,0 +1,116 @@
export const DefaultRemoveFilterValue = '-- remove filter --';
export class FilterSegments {
filterSegments: any[];
removeSegment: any;
defaultFilterValue = 'select value';
constructor(private uiSegmentSrv, private target, private getFilterKeysFunc, private getFilterValuesFunc) {}
buildSegmentModel() {
this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: DefaultRemoveFilterValue });
this.filterSegments = [];
this.target.filters.forEach((f, index) => {
switch (index % 4) {
case 0:
this.filterSegments.push(this.uiSegmentSrv.newKey(f));
break;
case 1:
this.filterSegments.push(this.uiSegmentSrv.newOperator(f));
break;
case 2:
this.filterSegments.push(this.uiSegmentSrv.newKeyValue(f));
break;
case 3:
this.filterSegments.push(this.uiSegmentSrv.newCondition(f));
break;
}
});
this.ensurePlusButton(this.filterSegments);
}
async getFilters(segment, index, hasNoFilterKeys) {
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') {
if (hasNoFilterKeys && segment.value && segment.value !== DefaultRemoveFilterValue) {
this.removeSegment.value = DefaultRemoveFilterValue;
return Promise.resolve([this.removeSegment]);
} else {
return this.getFilterKeysFunc();
}
}
if (segment.type === 'value') {
const filterValues = this.getFilterValuesFunc(index);
if (filterValues.length > 0) {
return this.getValuesForFilterKey(filterValues);
}
}
return [];
}
getValuesForFilterKey(labels: any[]) {
const filterValues = labels.map(l => {
return this.uiSegmentSrv.newSegment({
value: `${l}`,
expandable: false,
});
});
return filterValues;
}
addNewFilterSegments(segment, index) {
if (index > 2) {
this.filterSegments.splice(index, 0, this.uiSegmentSrv.newCondition('AND'));
}
segment.type = 'key';
this.filterSegments.push(this.uiSegmentSrv.newOperator('='));
this.filterSegments.push(this.uiSegmentSrv.newFake(this.defaultFilterValue, 'value', 'query-segment-value'));
}
removeFilterSegment(index) {
this.filterSegments.splice(index, 3);
// remove trailing condition
if (index > 2 && this.filterSegments[index - 1].type === 'condition') {
this.filterSegments.splice(index - 1, 1);
}
// remove condition if it is first segment
if (index === 0 && this.filterSegments[0].type === 'condition') {
this.filterSegments.splice(0, 1);
}
}
ensurePlusButton(segments) {
const count = segments.length;
const lastSegment = segments[Math.max(count - 1, 0)];
if (!lastSegment || lastSegment.type !== 'plus-button') {
segments.push(this.uiSegmentSrv.newPlusButton());
}
}
filterSegmentUpdated(segment, index) {
if (segment.type === 'plus-button') {
this.addNewFilterSegments(segment, index);
} else if (segment.type === 'key' && segment.value === DefaultRemoveFilterValue) {
this.removeFilterSegment(index);
this.ensurePlusButton(this.filterSegments);
} else if (segment.type === 'value' && segment.value !== this.defaultFilterValue) {
this.ensurePlusButton(this.filterSegments);
}
return this.filterSegments.filter(s => s.type !== 'plus-button').map(seg => seg.value);
}
}
@@ -1,4 +1,4 @@
<query-editor-row query-ctrl="ctrl" has-text-edit-mode="true">
<query-editor-row query-ctrl="ctrl" has-text-edit-mode="false">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Metric Type</span>
@@ -12,7 +12,7 @@
<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">
<div class="gf-form" ng-repeat="segment in ctrl.filterSegments.filterSegments">
<metric-segment segment="segment" get-options="ctrl.getFilters(segment, $index)" on-change="ctrl.filterSegmentUpdated(segment, $index)"></metric-segment>
</div>
</div>
@@ -2,7 +2,7 @@ import _ from 'lodash';
import { QueryCtrl } from 'app/plugins/sdk';
import appEvents from 'app/core/app_events';
import * as options from './constants';
// mport BaseComponent, * as extras from './A';
import { FilterSegments, DefaultRemoveFilterValue } from './filter_segments';
export interface QueryMeta {
rawQuery: string;
@@ -54,7 +54,6 @@ export class StackdriverQueryCtrl extends QueryCtrl {
};
groupBySegments: any[];
filterSegments: any[];
removeSegment: any;
showHelp: boolean;
showLastQuery: boolean;
@@ -62,6 +61,7 @@ export class StackdriverQueryCtrl extends QueryCtrl {
lastQueryError?: string;
metricLabels: { [key: string]: string[] };
resourceLabels: { [key: string]: string[] };
filterSegments: any;
/** @ngInject */
constructor($scope, $injector, private uiSegmentSrv, private timeSrv, private templateSrv) {
@@ -86,24 +86,13 @@ export class StackdriverQueryCtrl extends QueryCtrl {
this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' });
this.ensurePlusButton(this.groupBySegments);
this.filterSegments = [];
this.target.filters.forEach((f, index) => {
switch (index % 4) {
case 0:
this.filterSegments.push(this.uiSegmentSrv.newKey(f));
break;
case 1:
this.filterSegments.push(this.uiSegmentSrv.newOperator(f));
break;
case 2:
this.filterSegments.push(this.uiSegmentSrv.newKeyValue(f));
break;
case 3:
this.filterSegments.push(this.uiSegmentSrv.newCondition(f));
break;
}
});
this.ensurePlusButton(this.filterSegments);
this.filterSegments = new FilterSegments(
this.uiSegmentSrv,
this.target,
this.getGroupBys.bind(this, null, null, DefaultRemoveFilterValue, false),
this.getFilterValues.bind(this)
);
this.filterSegments.buildSegmentModel();
}
async getCurrentProject() {
@@ -238,95 +227,34 @@ export class StackdriverQueryCtrl extends QueryCtrl {
}
async getFilters(segment, index) {
if (segment.type === 'condition') {
return [this.uiSegmentSrv.newSegment('AND')];
const hasNoFilterKeys = this.metricLabels && Object.keys(this.metricLabels).length === 0;
return this.filterSegments.getFilters(segment, index, hasNoFilterKeys);
}
getFilterValues(index) {
const filterKey = this.templateSrv.replace(this.filterSegments.filterSegments[index - 2].value);
if (!filterKey || !this.metricLabels || Object.keys(this.metricLabels).length === 0) {
return [];
}
if (segment.type === 'operator') {
return this.uiSegmentSrv.newOperators(['=', '!=', '=~', '!=~']);
const shortKey = filterKey.substring(filterKey.indexOf('.label.') + 7);
if (filterKey.startsWith('metric.label.') && this.metricLabels.hasOwnProperty(shortKey)) {
return this.metricLabels[shortKey];
}
if (segment.type === 'key' || segment.type === 'plus-button') {
if (
this.metricLabels &&
Object.keys(this.metricLabels).length === 0 &&
segment.value &&
segment.value !== this.defaultRemoveFilterValue
) {
this.removeSegment.value = this.defaultRemoveFilterValue;
return Promise.resolve([this.removeSegment]);
} else {
return this.getGroupBys(null, null, this.defaultRemoveFilterValue, false);
}
}
if (segment.type === '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)) {
return this.getValuesForFilterKey(this.metricLabels[shortKey]);
}
if (filterKey.startsWith('resource.label.') && this.resourceLabels.hasOwnProperty(shortKey)) {
return this.getValuesForFilterKey(this.resourceLabels[shortKey]);
}
if (filterKey.startsWith('resource.label.') && this.resourceLabels.hasOwnProperty(shortKey)) {
return this.resourceLabels[shortKey];
}
return [];
}
getValuesForFilterKey(labels: any[]) {
const filterValues = labels.map(l => {
return this.uiSegmentSrv.newSegment({
value: `${l}`,
expandable: false,
});
});
return filterValues;
}
filterSegmentUpdated(segment, index) {
if (segment.type === 'plus-button') {
this.addNewFilterSegments(segment, index);
} else if (segment.type === 'key' && segment.value === this.defaultRemoveFilterValue) {
this.removeFilterSegment(index);
this.ensurePlusButton(this.filterSegments);
} else if (segment.type === 'value' && segment.value !== this.defaultFilterValue) {
this.ensurePlusButton(this.filterSegments);
}
this.target.filters = this.filterSegments.filter(s => s.type !== 'plus-button').map(seg => seg.value);
this.target.filters = this.filterSegments.filterSegmentUpdated(segment, index);
this.refresh();
}
addNewFilterSegments(segment, index) {
if (index > 2) {
this.filterSegments.splice(index, 0, this.uiSegmentSrv.newCondition('AND'));
}
segment.type = 'key';
this.filterSegments.push(this.uiSegmentSrv.newOperator('='));
this.filterSegments.push(this.uiSegmentSrv.newFake(this.defaultFilterValue, 'value', 'query-segment-value'));
}
removeFilterSegment(index) {
this.filterSegments.splice(index, 3);
// remove trailing condition
if (index > 2 && this.filterSegments[index - 1].type === 'condition') {
this.filterSegments.splice(index - 1, 1);
}
// remove condition if it is first segment
if (index === 0 && this.filterSegments[0].type === 'condition') {
this.filterSegments.splice(0, 1);
}
}
ensurePlusButton(segments) {
const count = segments.length;
const lastSegment = segments[Math.max(count - 1, 0)];
@@ -12,15 +12,15 @@ describe('StackdriverQueryCtrl', () => {
});
it('should initialize filter segments using the target filter values', () => {
expect(ctrl.filterSegments.length).toBe(8);
expect(ctrl.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments[3].type).toBe('condition');
expect(ctrl.filterSegments[4].type).toBe('key');
expect(ctrl.filterSegments[5].type).toBe('operator');
expect(ctrl.filterSegments[6].type).toBe('value');
expect(ctrl.filterSegments[7].type).toBe('plus-button');
expect(ctrl.filterSegments.filterSegments.length).toBe(8);
expect(ctrl.filterSegments.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments.filterSegments[3].type).toBe('condition');
expect(ctrl.filterSegments.filterSegments[4].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[5].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[6].type).toBe('value');
expect(ctrl.filterSegments.filterSegments[7].type).toBe('plus-button');
});
});
@@ -106,7 +106,7 @@ describe('StackdriverQueryCtrl', () => {
result = await ctrl.getFilters(segment, 0);
});
it('should populate group bys segments', () => {
it('should populate condition segments', () => {
expect(result.length).toBe(1);
expect(result[0].value).toBe('AND');
});
@@ -142,7 +142,7 @@ describe('StackdriverQueryCtrl', () => {
result = await ctrl.getFilters(segment, 0);
});
it('should populate group bys segments', () => {
it('should populate filter key 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');
@@ -163,13 +163,16 @@ describe('StackdriverQueryCtrl', () => {
'resource-key-2': ['resource-value-2'],
};
ctrl.filterSegments = [{ type: 'key', value: 'metric.label.metric-key-1' }, { type: 'operator', value: '=' }];
ctrl.filterSegments.filterSegments = [
{ type: 'key', value: 'metric.label.metric-key-1' },
{ type: 'operator', value: '=' },
];
const segment = { type: 'value' };
result = await ctrl.getFilters(segment, 2);
});
it('should populate group bys segments', () => {
it('should populate filter value segments', () => {
expect(result.length).toBe(1);
expect(result[0].value).toBe('metric-value-1');
});
@@ -179,18 +182,18 @@ describe('StackdriverQueryCtrl', () => {
describe('and there are no other filters', () => {
beforeEach(() => {
const segment = { value: 'filterkey1', type: 'plus-button' };
ctrl.filterSegments = [segment];
ctrl.filterSegments.filterSegments = [segment];
ctrl.filterSegmentUpdated(segment, 0);
});
it('should transform the plus button segment to a key segment', () => {
expect(ctrl.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[0].type).toBe('key');
});
it('should add an operator, value segment and plus button segment', () => {
expect(ctrl.filterSegments.length).toBe(3);
expect(ctrl.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments.filterSegments.length).toBe(3);
expect(ctrl.filterSegments.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[2].type).toBe('value');
});
});
});
@@ -201,15 +204,20 @@ describe('StackdriverQueryCtrl', () => {
const existingOperatorSegment = { value: '=', type: 'operator' };
const existingValueSegment = { value: 'filtervalue', type: 'value' };
const plusSegment = { value: '', type: 'plus-button' };
ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment, plusSegment];
ctrl.filterSegments.filterSegments = [
existingKeySegment,
existingOperatorSegment,
existingValueSegment,
plusSegment,
];
ctrl.filterSegmentUpdated(existingKeySegment, 0);
});
it('should not add any new segments', () => {
expect(ctrl.filterSegments.length).toBe(4);
expect(ctrl.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments.filterSegments.length).toBe(4);
expect(ctrl.filterSegments.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[2].type).toBe('value');
});
});
describe('and user clicks on value segment and value not equal to fake value', () => {
@@ -217,16 +225,16 @@ describe('StackdriverQueryCtrl', () => {
const existingKeySegment = { value: 'filterkey1', type: 'key' };
const existingOperatorSegment = { value: '=', type: 'operator' };
const existingValueSegment = { value: 'filtervalue', type: 'value' };
ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment];
ctrl.filterSegments.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment];
ctrl.filterSegmentUpdated(existingValueSegment, 2);
});
it('should ensure that plus segment exists', () => {
expect(ctrl.filterSegments.length).toBe(4);
expect(ctrl.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments[3].type).toBe('plus-button');
expect(ctrl.filterSegments.filterSegments.length).toBe(4);
expect(ctrl.filterSegments.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments.filterSegments[3].type).toBe('plus-button');
});
});
@@ -235,15 +243,15 @@ describe('StackdriverQueryCtrl', () => {
const existingKeySegment = { value: 'filterkey1', type: 'key' };
const existingOperatorSegment = { value: '=', type: 'operator' };
const existingValueSegment = { value: ctrl.defaultFilterValue, type: 'value' };
ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment];
ctrl.filterSegments.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment];
ctrl.filterSegmentUpdated(existingValueSegment, 2);
});
it('should not add plus segment', () => {
expect(ctrl.filterSegments.length).toBe(3);
expect(ctrl.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments.filterSegments.length).toBe(3);
expect(ctrl.filterSegments.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[2].type).toBe('value');
});
});
describe('and user removes key segment', () => {
@@ -252,13 +260,18 @@ describe('StackdriverQueryCtrl', () => {
const existingOperatorSegment = { value: '=', type: 'operator' };
const existingValueSegment = { value: 'filtervalue', type: 'value' };
const plusSegment = { value: '', type: 'plus-button' };
ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment, plusSegment];
ctrl.filterSegments.filterSegments = [
existingKeySegment,
existingOperatorSegment,
existingValueSegment,
plusSegment,
];
ctrl.filterSegmentUpdated(existingKeySegment, 0);
});
it('should remove filter segments', () => {
expect(ctrl.filterSegments.length).toBe(1);
expect(ctrl.filterSegments[0].type).toBe('plus-button');
expect(ctrl.filterSegments.filterSegments.length).toBe(1);
expect(ctrl.filterSegments.filterSegments[0].type).toBe('plus-button');
});
});
@@ -270,7 +283,7 @@ describe('StackdriverQueryCtrl', () => {
const existingValueSegment = { value: 'filtervalue', type: 'value' };
const conditionSegment = { value: 'AND', type: 'condition' };
const plusSegment = { value: '', type: 'plus-button' };
ctrl.filterSegments = [
ctrl.filterSegments.filterSegments = [
existingKeySegment1,
existingOperatorSegment,
existingValueSegment,
@@ -284,11 +297,11 @@ describe('StackdriverQueryCtrl', () => {
});
it('should remove filter segments and the condition segment', () => {
expect(ctrl.filterSegments.length).toBe(4);
expect(ctrl.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments[3].type).toBe('plus-button');
expect(ctrl.filterSegments.filterSegments.length).toBe(4);
expect(ctrl.filterSegments.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments.filterSegments[3].type).toBe('plus-button');
});
});
@@ -300,7 +313,7 @@ describe('StackdriverQueryCtrl', () => {
const existingValueSegment = { value: 'filtervalue', type: 'value' };
const conditionSegment = { value: 'AND', type: 'condition' };
const plusSegment = { value: '', type: 'plus-button' };
ctrl.filterSegments = [
ctrl.filterSegments.filterSegments = [
existingKeySegment1,
existingOperatorSegment,
existingValueSegment,
@@ -314,11 +327,11 @@ describe('StackdriverQueryCtrl', () => {
});
it('should remove filter segments and the condition segment', () => {
expect(ctrl.filterSegments.length).toBe(4);
expect(ctrl.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments[3].type).toBe('plus-button');
expect(ctrl.filterSegments.filterSegments.length).toBe(4);
expect(ctrl.filterSegments.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments.filterSegments[3].type).toBe('plus-button');
});
});
@@ -328,19 +341,24 @@ describe('StackdriverQueryCtrl', () => {
const existingOperatorSegment = { value: '=', type: 'operator' };
const existingValueSegment = { value: 'filtervalue', type: 'value' };
const plusSegment = { value: 'filterkey2', type: 'plus-button' };
ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment, plusSegment];
ctrl.filterSegments.filterSegments = [
existingKeySegment,
existingOperatorSegment,
existingValueSegment,
plusSegment,
];
ctrl.filterSegmentUpdated(plusSegment, 3);
});
it('should condition segment and new filter segments', () => {
expect(ctrl.filterSegments.length).toBe(7);
expect(ctrl.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments[3].type).toBe('condition');
expect(ctrl.filterSegments[4].type).toBe('key');
expect(ctrl.filterSegments[5].type).toBe('operator');
expect(ctrl.filterSegments[6].type).toBe('value');
expect(ctrl.filterSegments.filterSegments.length).toBe(7);
expect(ctrl.filterSegments.filterSegments[0].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[1].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[2].type).toBe('value');
expect(ctrl.filterSegments.filterSegments[3].type).toBe('condition');
expect(ctrl.filterSegments.filterSegments[4].type).toBe('key');
expect(ctrl.filterSegments.filterSegments[5].type).toBe('operator');
expect(ctrl.filterSegments.filterSegments[6].type).toBe('value');
});
});
});