diff --git a/public/app/plugins/datasource/stackdriver/filter_segments.ts b/public/app/plugins/datasource/stackdriver/filter_segments.ts new file mode 100644 index 00000000000..0aed1262907 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/filter_segments.ts @@ -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); + } +} diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index c6804f3f8e3..5a87f981119 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -1,4 +1,4 @@ - +
Metric Type @@ -12,7 +12,7 @@
Filter -
+
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 3a5cd43f1c8..6bc378fffa7 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -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)]; diff --git a/public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts b/public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts index dc4740fb6c3..1335a9fccb3 100644 --- a/public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts @@ -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'); }); }); });