瀏覽代碼

stackdriver: wip - filters for query editor

Daniel Lee 7 年之前
父節點
當前提交
b5800ffea9

+ 15 - 7
public/app/plugins/datasource/stackdriver/partials/query.editor.html

@@ -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>

+ 67 - 11
public/app/plugins/datasource/stackdriver/query_ctrl.ts

@@ -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)];

+ 132 - 47
public/app/plugins/datasource/stackdriver/specs/query_ctrl.test.ts

@@ -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 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();
+    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);
+      });
     });
 
-    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 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 is selected', () => {
-    beforeEach(() => {
-      const removeSegment = { fake: true, value: '-- remove group by --' };
-      const segment = { value: 'groupby1' };
-      ctrl.groupBySegments = [segment, removeSegment];
-      ctrl.groupByChanged(segment);
+  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');
+      });
     });
 
-    it('should be added to group bys list', () => {
-      expect(ctrl.target.aggregation.groupBys.length).toBe(1);
+    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 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);
+    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 --');
+      });
     });
 
-    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: [],
   };
 }