فهرست منبع

break out metric picker and filter

Erik Sundell 7 سال پیش
والد
کامیت
ad55be9865

+ 181 - 0
public/app/plugins/datasource/stackdriver/components/MetricPicker.tsx

@@ -0,0 +1,181 @@
+import React from 'react';
+import _ from 'lodash';
+
+import { OptionPicker } from './OptionPicker';
+import { OptionGroupPicker } from './OptionGroupPicker';
+
+export interface Props {
+  onChange: (metricDescriptor) => void;
+  templateSrv: any;
+  datasource: any;
+  defaultProject: string;
+  metricType: string;
+}
+
+interface State {
+  metricDescriptors: any[];
+  metrics: any[];
+  services: any[];
+  service: string;
+  metric: string;
+}
+
+export class MetricPicker extends React.Component<Props, State> {
+  state: State = {
+    metricDescriptors: [],
+    metrics: [],
+    services: [],
+    service: '',
+    metric: '',
+  };
+
+  constructor(props) {
+    super(props);
+  }
+
+  componentDidMount() {
+    this.getCurrentProject()
+      .then(this.loadMetricDescriptors.bind(this))
+      .then(this.initializeServiceAndMetrics.bind(this));
+    // .then(this.getLabels.bind(this));
+  }
+
+  async getCurrentProject() {
+    return new Promise(async (resolve, reject) => {
+      try {
+        if (!this.props.defaultProject || this.props.defaultProject === 'loading project...') {
+          // this.props.defaultProject = await this.props.datasource.getDefaultProject();
+          await this.props.datasource.getDefaultProject();
+        }
+        resolve(this.props.defaultProject);
+      } catch (error) {
+        // appEvents.emit('ds-request-error', error);
+        reject();
+      }
+    });
+  }
+
+  async loadMetricDescriptors() {
+    if (this.props.defaultProject !== 'loading project...') {
+      const metricDescriptors = await this.props.datasource.getMetricTypes(this.props.defaultProject);
+      this.setState({ metricDescriptors });
+      return metricDescriptors;
+    } else {
+      return [];
+    }
+  }
+
+  async initializeServiceAndMetrics() {
+    const { metricDescriptors } = this.state;
+    const services = this.getServicesList(metricDescriptors);
+    const metrics = this.getMetricsList(metricDescriptors);
+    const service = metrics.length > 0 ? metrics[0].service : '';
+    this.setState({ metricDescriptors, services, metrics, service: service });
+  }
+
+  getMetricsList(metricDescriptors) {
+    const selectedMetricDescriptor = metricDescriptors.find(md => md.type === this.props.metricType);
+    const metricsByService = metricDescriptors.filter(m => m.service === selectedMetricDescriptor.service).map(m => ({
+      service: m.service,
+      value: m.type,
+      label: m.displayName,
+      description: m.description,
+    }));
+    return metricsByService;
+  }
+
+  handleServiceChange(service) {
+    console.log('handleServiceChange', service);
+    const { metricDescriptors } = this.state;
+    const { templateSrv, metricType } = this.props;
+
+    const metrics = metricDescriptors.filter(m => m.service === templateSrv.replace(service)).map(m => ({
+      service: m.service,
+      value: m.type,
+      label: m.displayName,
+      description: m.description,
+    }));
+
+    this.setState({ service, metrics });
+
+    if (metrics.length > 0 && !metrics.some(m => m.value === templateSrv.replace(metricType))) {
+      this.handleMetricTypeChange(metrics[0].value);
+    }
+  }
+
+  handleMetricTypeChange(value) {
+    const selectedMetricDescriptor = this.state.metricDescriptors.find(md => md.type === value);
+    this.props.onChange(selectedMetricDescriptor);
+  }
+
+  getServicesList(metricDescriptors) {
+    const services = metricDescriptors.map(m => ({
+      value: m.service,
+      label: _.startCase(m.serviceShortName),
+    }));
+
+    return services.length > 0 ? _.uniqBy(services, s => s.value) : [];
+  }
+
+  getTemplateVariablesGroup() {
+    return {
+      label: 'Template Variables',
+      options: this.props.templateSrv.variables.map(v => ({
+        label: `$${v.name}`,
+        value: `$${v.name}`,
+      })),
+    };
+  }
+
+  getMetricGroups() {
+    return [
+      this.getTemplateVariablesGroup(),
+      {
+        label: 'Metrics',
+        options: this.state.metrics,
+      },
+    ];
+  }
+
+  render() {
+    const { services, service } = this.state;
+    const { metricType } = this.props;
+
+    return (
+      <React.Fragment>
+        <div className="gf-form-inline">
+          <div className="gf-form">
+            <span className="gf-form-label width-9 query-keyword">Service</span>
+            <OptionPicker
+              onChange={value => this.handleServiceChange(value)}
+              selected={service}
+              options={services}
+              searchable={false}
+              placeholder="Select Services"
+              className="width-15"
+            />
+          </div>
+          <div className="gf-form gf-form--grow">
+            <div className="gf-form-label gf-form-label--grow" />
+          </div>
+        </div>
+        <div className="gf-form-inline">
+          <div className="gf-form">
+            <span className="gf-form-label width-9 query-keyword">Metric</span>
+            <OptionGroupPicker
+              onChange={value => this.handleMetricTypeChange(value)}
+              selected={metricType}
+              groups={this.getMetricGroups()}
+              searchable={true}
+              placeholder="Select Metric"
+              className="width-15"
+            />
+          </div>
+          <div className="gf-form gf-form--grow">
+            <div className="gf-form-label gf-form-label--grow" />
+          </div>
+        </div>
+      </React.Fragment>
+    );
+  }
+}

+ 5 - 1
public/app/plugins/datasource/stackdriver/partials/query.editor.html

@@ -1,7 +1,11 @@
 <query-editor-row query-ctrl="ctrl" has-text-edit-mode="false">
-  <stackdriver-filter target="ctrl.target" refresh="ctrl.refresh()" datasource="ctrl.datasource"
+  <metric-picker target="ctrl.target" default-project="ctrl.target.defaultProject"  metric-type="ctrl.target.metricType" template-srv="ctrl.templateSrv" datasource="ctrl.datasource" on-change="ctrl.handleMetricTypeChange"></metric-picker>
+  <stackdriver-filter target="ctrl.target" refresh="ctrl.refresh()" loading="ctrl.loadLabelsPromise" label-data="ctrl.labelData"
     ></stackdriver-filter>
   <stackdriver-aggregation target="ctrl.target" alignment-period="ctrl.lastQueryMeta.alignmentPeriod" refresh="ctrl.refresh()"></stackdriver-aggregation>
+  <!-- <stackdriver-filter target="ctrl.target" refresh="ctrl.refresh()" datasource="ctrl.datasource"
+    ></stackdriver-filter>
+  <stackdriver-aggregation target="ctrl.target" alignment-period="ctrl.lastQueryMeta.alignmentPeriod" refresh="ctrl.refresh()"></stackdriver-aggregation> -->
   <div class="gf-form-inline">
     <div class="gf-form">
       <span class="gf-form-label query-keyword width-9">Alias By</span>

+ 28 - 26
public/app/plugins/datasource/stackdriver/partials/query.filter.html

@@ -1,31 +1,33 @@
-<div class="gf-form-inline">
-  <div class="gf-form">
-    <span class="gf-form-label width-9 query-keyword">Service</span>
-    <option-picker
-      onChange="ctrl.handleServiceChange"
-      selected="ctrl.target.service"
-      options="ctrl.services"
-      searchable="false"
-      placeholder="'Select Services'"
-      className="'width-15'"
-    ></option-picker>
+<!--
+  <div class="gf-form-inline">
+    <div class="gf-form">
+      <span class="gf-form-label width-9 query-keyword">Service</span>
+      <option-picker
+        onChange="ctrl.handleServiceChange"
+        selected="ctrl.target.service"
+        options="ctrl.services"
+        searchable="false"
+        placeholder="'Select Services'"
+        className="'width-15'"
+      ></option-picker>
+    </div>
+    <div class="gf-form gf-form--grow"><div class="gf-form-label gf-form-label--grow"></div></div>
   </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 width-9 query-keyword">Metric</span>
-    <option-group-picker
-      onChange="ctrl.handleMetricTypeChange"
-      selected="ctrl.target.metricType"
-      groups="ctrl.getMetricGroups()"
-      searchable="true"
-      placeholder="'Select Metric'"
-      className="'width-15'"
-    ></option-group-picker>
+  <div class="gf-form-inline">
+    <div class="gf-form">
+      <span class="gf-form-label width-9 query-keyword">Metric</span>
+      <option-group-picker
+        onChange="ctrl.handleMetricTypeChange"
+        selected="ctrl.target.metricType"
+        groups="ctrl.getMetricGroups()"
+        searchable="true"
+        placeholder="'Select Metric'"
+        className="'width-15'"
+      ></option-group-picker>
+    </div>
+    <div class="gf-form gf-form--grow"><div class="gf-form-label gf-form-label--grow"></div></div>
   </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>

+ 0 - 1
public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts

@@ -52,7 +52,6 @@ export class StackdriverAggregationCtrl {
   }
 
   setAlignOptions() {
-    console.log('this.target.metricKind', this.target.metricKind);
     const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind).map(a => ({
       ...a,
       label: a.text,

+ 46 - 1
public/app/plugins/datasource/stackdriver/query_ctrl.ts

@@ -1,7 +1,9 @@
 import _ from 'lodash';
+import appEvents from 'app/core/app_events';
 import { QueryCtrl } from 'app/plugins/sdk';
 import './query_aggregation_ctrl';
 import './query_filter_ctrl';
+import { MetricPicker } from './components/MetricPicker';
 import { OptionPicker } from './components/OptionPicker';
 import { OptionGroupPicker } from './components/OptionGroupPicker';
 import { react2AngularDirective } from 'app/core/utils/react2angular';
@@ -57,13 +59,19 @@ export class StackdriverQueryCtrl extends QueryCtrl {
   showLastQuery: boolean;
   lastQueryMeta: QueryMeta;
   lastQueryError?: string;
+  labelData: QueryMeta;
+
+  loadLabelsPromise: Promise<any>;
+  templateSrv: any;
 
   /** @ngInject */
-  constructor($scope, $injector) {
+  constructor($scope, $injector, templateSrv, private $rootScope) {
     super($scope, $injector);
+    this.templateSrv = templateSrv;
     _.defaultsDeep(this.target, this.defaults);
     this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope);
     this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope);
+    this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this);
     react2AngularDirective('optionPicker', OptionPicker, [
       'options',
       'onChange',
@@ -80,6 +88,43 @@ export class StackdriverQueryCtrl extends QueryCtrl {
       'className',
       'placeholder',
     ]);
+    react2AngularDirective('metricPicker', MetricPicker, [
+      'target',
+      ['onChange', { watchDepth: 'reference' }],
+      'defaultProject',
+      'metricType',
+      ['templateSrv', { watchDepth: 'reference' }],
+      ['datasource', { watchDepth: 'reference' }],
+    ]);
+    this.getLabels();
+  }
+
+  handleMetricTypeChange({ valueType, metricKind, type, unit }) {
+    this.target.metricType = type;
+    this.target.unit = unit;
+    this.target.valueType = valueType;
+    this.target.metricKind = metricKind;
+    this.$rootScope.$broadcast('metricTypeChanged');
+    this.getLabels();
+    this.refresh();
+  }
+
+  async getLabels() {
+    this.loadLabelsPromise = new Promise(async resolve => {
+      try {
+        const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId);
+        this.labelData = meta;
+        resolve();
+      } catch (error) {
+        if (error.data && error.data.message) {
+          console.log(error.data.message);
+        } else {
+          console.log(error);
+        }
+        appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]);
+        resolve();
+      }
+    });
   }
 
   onDataReceived(dataList) {

+ 46 - 149
public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts

@@ -1,7 +1,8 @@
 import coreModule from 'app/core/core_module';
 import _ from 'lodash';
 import { FilterSegments } from './filter_segments';
-import appEvents from 'app/core/app_events';
+import { QueryMeta } from './query_ctrl';
+// import appEvents from 'app/core/app_events';
 
 export class StackdriverFilter {
   /** @ngInject */
@@ -12,8 +13,9 @@ export class StackdriverFilter {
       controllerAs: 'ctrl',
       restrict: 'E',
       scope: {
+        labelData: '<',
+        loading: '<',
         target: '=',
-        datasource: '=',
         refresh: '&',
         hideGroupBys: '<',
       },
@@ -28,41 +30,26 @@ export class StackdriverFilterCtrl {
 
   defaultRemoveGroupByValue = '-- remove group by --';
   resourceTypeValue = 'resource.type';
-  loadLabelsPromise: Promise<any>;
 
   metricDescriptors: any[];
   metrics: any[];
-  metricGroups: any[];
   services: any[];
   groupBySegments: any[];
   filterSegments: FilterSegments;
   removeSegment: any;
   target: any;
-  datasource: any;
+
+  labelData: QueryMeta;
+  loading: Promise<any>;
 
   /** @ngInject */
   constructor(private $scope, private uiSegmentSrv, private templateSrv, private $rootScope) {
-    this.datasource = $scope.datasource;
     this.target = $scope.target;
     this.metricDescriptors = [];
     this.metrics = [];
-    this.metricGroups = [];
     this.services = [];
 
-    this.getCurrentProject()
-      .then(this.loadMetricDescriptors.bind(this))
-      .then(this.getLabels.bind(this));
-
     this.initSegments($scope.hideGroupBys);
-    this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this);
-    this.handleServiceChange = this.handleServiceChange.bind(this);
-  }
-
-  handleMetricTypeChange(value) {
-    this.target.metricType = value;
-    this.setMetricType();
-    this.$scope.refresh();
-    this.getLabels();
   }
 
   initSegments(hideGroupBys: boolean) {
@@ -84,129 +71,34 @@ export class StackdriverFilterCtrl {
     this.filterSegments.buildSegmentModel();
   }
 
-  async getCurrentProject() {
-    return new Promise(async (resolve, reject) => {
-      try {
-        if (!this.target.defaultProject || this.target.defaultProject === 'loading project...') {
-          this.target.defaultProject = await this.datasource.getDefaultProject();
-        }
-        resolve(this.target.defaultProject);
-      } catch (error) {
-        appEvents.emit('ds-request-error', error);
-        reject();
-      }
-    });
-  }
-
-  async loadMetricDescriptors() {
-    if (this.target.defaultProject !== 'loading project...') {
-      this.metricDescriptors = await this.datasource.getMetricTypes(this.target.defaultProject);
-      this.services = this.getServicesList();
-      this.metrics = this.getMetricsList();
-      this.metricGroups = this.getMetricGroups();
-      return this.metricDescriptors;
-    } else {
-      return [];
-    }
-  }
-
-  getServicesList() {
-    const services = this.metricDescriptors.map(m => ({
-      value: m.service,
-      label: _.startCase(m.serviceShortName),
-    }));
-
-    return services.length > 0 ? _.uniqBy(services, s => s.value) : [];
-  }
-
-  getMetricGroups() {
-    return [
-      this.getTemplateVariablesGroup(),
-      {
-        label: 'Metrics',
-        options: this.getMetricsList(),
-      },
-    ];
-  }
-
-  getTemplateVariablesGroup() {
-    return {
-      label: 'Template Variables',
-      options: this.templateSrv.variables.map(v => ({
-        label: `$${v.name}`,
-        value: `$${v.name}`,
-      })),
-    };
-  }
-
-  getMetricsList() {
-    const metricsByService = this.metricDescriptors.filter(m => m.service === this.target.service).map(m => ({
-      service: m.service,
-      value: m.type,
-      label: m.displayName,
-      description: m.description,
-    }));
-
-    if (
-      metricsByService.length > 0 &&
-      !metricsByService.some(m => m.value === this.templateSrv.replace(this.target.metricType))
-    ) {
-      this.target.metricType = metricsByService[0].value;
-    }
-    return metricsByService;
-  }
-
-  async getLabels() {
-    this.loadLabelsPromise = new Promise(async resolve => {
-      try {
-        if (this.target.metricType) {
-          const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId);
-          this.metricLabels = meta.metricLabels;
-          this.resourceLabels = meta.resourceLabels;
-          this.resourceTypes = meta.resourceTypes;
-          resolve();
-        } else {
-          resolve();
-        }
-      } catch (error) {
-        if (error.data && error.data.message) {
-          console.log(error.data.message);
-        } else {
-          console.log(error);
-        }
-        appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]);
-        resolve();
-      }
-    });
-  }
-
-  handleServiceChange(service) {
-    this.target.service = service;
-    this.metrics = this.getMetricsList();
-    this.metricGroups = this.getMetricGroups();
-    this.setMetricType();
-    this.getLabels();
-    if (!this.metrics.some(m => m.value === this.target.metricType)) {
-      this.target.metricType = '';
-    } else {
-      this.$scope.refresh();
-    }
-  }
-
-  setMetricType() {
-    const { valueType, metricKind, unit } = this.metricDescriptors.find(
-      m => m.type === this.templateSrv.replace(this.target.metricType)
-    );
-    this.target.unit = unit;
-    this.target.valueType = valueType;
-    this.target.metricKind = metricKind;
-    this.$rootScope.$broadcast('metricTypeChanged');
-  }
+  // async getLabels() {
+  //   this.loadLabelsPromise = new Promise(async resolve => {
+  //     try {
+  //       if (this.target.metricType) {
+  //         const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId);
+  //         this.$scope.labelData.metricLabels = meta.metricLabels;
+  //         this.$scope.labelData.resourceLabels = meta.resourceLabels;
+  //         this.$scope.labelData.resourceTypes = meta.resourceTypes;
+  //         resolve();
+  //       } else {
+  //         resolve();
+  //       }
+  //     } catch (error) {
+  //       if (error.data && error.data.message) {
+  //         console.log(error.data.message);
+  //       } else {
+  //         console.log(error);
+  //       }
+  //       appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]);
+  //       resolve();
+  //     }
+  //   });
+  // }
 
   async createLabelKeyElements() {
-    await this.loadLabelsPromise;
+    await this.$scope.loading;
 
-    let elements = Object.keys(this.metricLabels || {}).map(l => {
+    let elements = Object.keys(this.$scope.labelData.metricLabels || {}).map(l => {
       return this.uiSegmentSrv.newSegment({
         value: `metric.label.${l}`,
         expandable: false,
@@ -215,7 +107,7 @@ export class StackdriverFilterCtrl {
 
     elements = [
       ...elements,
-      ...Object.keys(this.resourceLabels || {}).map(l => {
+      ...Object.keys(this.$scope.labelData.resourceLabels || {}).map(l => {
         return this.uiSegmentSrv.newSegment({
           value: `resource.label.${l}`,
           expandable: false,
@@ -223,7 +115,7 @@ export class StackdriverFilterCtrl {
       }),
     ];
 
-    if (this.resourceTypes && this.resourceTypes.length > 0) {
+    if (this.$scope.labelData.resourceTypes && this.$scope.labelData.resourceTypes.length > 0) {
       elements = [
         ...elements,
         this.uiSegmentSrv.newSegment({
@@ -288,28 +180,33 @@ export class StackdriverFilterCtrl {
   }
 
   async getFilters(segment, index) {
-    const hasNoFilterKeys = this.metricLabels && Object.keys(this.metricLabels).length === 0;
+    const hasNoFilterKeys =
+      this.$scope.labelData.metricLabels && Object.keys(this.$scope.labelData.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) {
+    if (
+      !filterKey ||
+      !this.$scope.labelData.metricLabels ||
+      Object.keys(this.$scope.labelData.metricLabels).length === 0
+    ) {
       return [];
     }
 
     const shortKey = filterKey.substring(filterKey.indexOf('.label.') + 7);
 
-    if (filterKey.startsWith('metric.label.') && this.metricLabels.hasOwnProperty(shortKey)) {
-      return this.metricLabels[shortKey];
+    if (filterKey.startsWith('metric.label.') && this.$scope.labelData.metricLabels.hasOwnProperty(shortKey)) {
+      return this.$scope.labelData.metricLabels[shortKey];
     }
 
-    if (filterKey.startsWith('resource.label.') && this.resourceLabels.hasOwnProperty(shortKey)) {
-      return this.resourceLabels[shortKey];
+    if (filterKey.startsWith('resource.label.') && this.$scope.labelData.resourceLabels.hasOwnProperty(shortKey)) {
+      return this.$scope.labelData.resourceLabels[shortKey];
     }
 
     if (filterKey === this.resourceTypeValue) {
-      return this.resourceTypes;
+      return this.$scope.labelData.resourceTypes;
     }
 
     return [];

+ 330 - 0
public/app/plugins/datasource/stackdriver/query_filter_ctrl2.ts

@@ -0,0 +1,330 @@
+// import coreModule from 'app/core/core_module';
+// import _ from 'lodash';
+// import { FilterSegments } from './filter_segments';
+// import appEvents from 'app/core/app_events';
+
+// export class StackdriverFilter {
+//   /** @ngInject */
+//   constructor() {
+//     return {
+//       templateUrl: 'public/app/plugins/datasource/stackdriver/partials/query.filter.html',
+//       controller: 'StackdriverFilterCtrl',
+//       controllerAs: 'ctrl',
+//       restrict: 'E',
+//       scope: {
+//         target: '=',
+//         datasource: '=',
+//         refresh: '&',
+//         hideGroupBys: '<',
+//       },
+//     };
+//   }
+// }
+
+// export class StackdriverFilterCtrl {
+//   metricLabels: { [key: string]: string[] };
+//   resourceLabels: { [key: string]: string[] };
+//   resourceTypes: string[];
+
+//   defaultRemoveGroupByValue = '-- remove group by --';
+//   resourceTypeValue = 'resource.type';
+//   loadLabelsPromise: Promise<any>;
+
+//   metricDescriptors: any[];
+//   metrics: any[];
+//   services: any[];
+//   groupBySegments: any[];
+//   filterSegments: FilterSegments;
+//   removeSegment: any;
+//   target: any;
+//   datasource: any;
+
+//   /** @ngInject */
+//   constructor(private $scope, private uiSegmentSrv, private templateSrv, private $rootScope) {
+//     this.datasource = $scope.datasource;
+//     this.target = $scope.target;
+//     this.metricDescriptors = [];
+//     this.metrics = [];
+//     this.services = [];
+
+//     this.getCurrentProject()
+//       .then(this.loadMetricDescriptors.bind(this))
+//       .then(this.getLabels.bind(this));
+
+//     this.initSegments($scope.hideGroupBys);
+//     this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this);
+//     this.handleServiceChange = this.handleServiceChange.bind(this);
+//   }
+
+//   handleMetricTypeChange(value) {
+//     this.target.metricType = value;
+//     this.setMetricType();
+//     this.$scope.refresh();
+//     this.getLabels();
+//   }
+
+//   initSegments(hideGroupBys: boolean) {
+//     if (!hideGroupBys) {
+//       this.groupBySegments = this.target.aggregation.groupBys.map(groupBy => {
+//         return this.uiSegmentSrv.getSegmentForValue(groupBy);
+//       });
+//       this.ensurePlusButton(this.groupBySegments);
+//     }
+
+//     this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' });
+
+//     this.filterSegments = new FilterSegments(
+//       this.uiSegmentSrv,
+//       this.target,
+//       this.getFilterKeys.bind(this),
+//       this.getFilterValues.bind(this)
+//     );
+//     this.filterSegments.buildSegmentModel();
+//   }
+
+//   async getCurrentProject() {
+//     return new Promise(async (resolve, reject) => {
+//       try {
+//         if (!this.target.defaultProject || this.target.defaultProject === 'loading project...') {
+//           this.target.defaultProject = await this.datasource.getDefaultProject();
+//         }
+//         resolve(this.target.defaultProject);
+//       } catch (error) {
+//         appEvents.emit('ds-request-error', error);
+//         reject();
+//       }
+//     });
+//   }
+
+//   async loadMetricDescriptors() {
+//     if (this.target.defaultProject !== 'loading project...') {
+//       this.metricDescriptors = await this.datasource.getMetricTypes(this.target.defaultProject);
+//       this.services = this.getServicesList();
+//       this.metrics = this.getMetricsList();
+//       return this.metricDescriptors;
+//     } else {
+//       return [];
+//     }
+//   }
+
+//   getServicesList() {
+//     const services = this.metricDescriptors.map(m => ({
+//       value: m.service,
+//       label: _.startCase(m.serviceShortName),
+//     }));
+
+//     return services.length > 0 ? _.uniqBy(services, s => s.value) : [];
+//   }
+
+//   getMetricGroups() {
+//     return [
+//       this.getTemplateVariablesGroup(),
+//       {
+//         label: 'Metrics',
+//         options: this.getMetricsList(),
+//       },
+//     ];
+//   }
+
+//   getTemplateVariablesGroup() {
+//     return {
+//       label: 'Template Variables',
+//       options: this.templateSrv.variables.map(v => ({
+//         label: `$${v.name}`,
+//         value: `$${v.name}`,
+//       })),
+//     };
+//   }
+
+//   getMetricsList() {
+//     const metricsByService = this.metricDescriptors.filter(m => m.service === this.target.service).map(m => ({
+//       service: m.service,
+//       value: m.type,
+//       label: m.displayName,
+//       description: m.description,
+//     }));
+
+//     if (
+//       metricsByService.length > 0 &&
+//       !metricsByService.some(m => m.value === this.templateSrv.replace(this.target.metricType))
+//     ) {
+//       // this.target.metricType = metricsByService[0].value;
+//     }
+//     return metricsByService;
+//   }
+
+//   async getLabels() {
+//     this.loadLabelsPromise = new Promise(async resolve => {
+//       try {
+//         if (this.target.metricType) {
+//           const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId);
+//           this.metricLabels = meta.metricLabels;
+//           this.resourceLabels = meta.resourceLabels;
+//           this.resourceTypes = meta.resourceTypes;
+//           resolve();
+//         } else {
+//           resolve();
+//         }
+//       } catch (error) {
+//         if (error.data && error.data.message) {
+//           console.log(error.data.message);
+//         } else {
+//           console.log(error);
+//         }
+//         appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]);
+//         resolve();
+//       }
+//     });
+//   }
+
+//   handleServiceChange(service) {
+//     this.target.service = service;
+//     this.metrics = this.getMetricsList();
+//     this.setMetricType();
+//     this.getLabels();
+//     if (!this.metrics.some(m => m.value === this.target.metricType)) {
+//       this.target.metricType = '';
+//     } else {
+//       this.$scope.refresh();
+//     }
+//   }
+
+//   setMetricType() {
+//     const { valueType, metricKind, unit } = this.metricDescriptors.find(
+//       m => m.type === this.templateSrv.replace(this.target.metricType)
+//     );
+//     this.target.unit = unit;
+//     this.target.valueType = valueType;
+//     this.target.metricKind = metricKind;
+//     this.$rootScope.$broadcast('metricTypeChanged');
+//   }
+
+//   async createLabelKeyElements() {
+//     await this.loadLabelsPromise;
+
+//     let elements = Object.keys(this.metricLabels || {}).map(l => {
+//       return this.uiSegmentSrv.newSegment({
+//         value: `metric.label.${l}`,
+//         expandable: false,
+//       });
+//     });
+
+//     elements = [
+//       ...elements,
+//       ...Object.keys(this.resourceLabels || {}).map(l => {
+//         return this.uiSegmentSrv.newSegment({
+//           value: `resource.label.${l}`,
+//           expandable: false,
+//         });
+//       }),
+//     ];
+
+//     if (this.resourceTypes && this.resourceTypes.length > 0) {
+//       elements = [
+//         ...elements,
+//         this.uiSegmentSrv.newSegment({
+//           value: this.resourceTypeValue,
+//           expandable: false,
+//         }),
+//       ];
+//     }
+
+//     return elements;
+//   }
+
+//   async getFilterKeys(segment, removeText?: string) {
+//     let elements = await this.createLabelKeyElements();
+
+//     if (this.target.filters.indexOf(this.resourceTypeValue) !== -1) {
+//       elements = elements.filter(e => e.value !== this.resourceTypeValue);
+//     }
+
+//     const noValueOrPlusButton = !segment || segment.type === 'plus-button';
+//     if (noValueOrPlusButton && elements.length === 0) {
+//       return [];
+//     }
+
+//     return [
+//       ...elements,
+//       this.uiSegmentSrv.newSegment({ fake: true, value: removeText || this.defaultRemoveGroupByValue }),
+//     ];
+//   }
+
+//   async getGroupBys(segment) {
+//     let elements = await this.createLabelKeyElements();
+
+//     elements = elements.filter(e => this.target.aggregation.groupBys.indexOf(e.value) === -1);
+//     const noValueOrPlusButton = !segment || segment.type === 'plus-button';
+//     if (noValueOrPlusButton && elements.length === 0) {
+//       return [];
+//     }
+
+//     this.removeSegment.value = this.defaultRemoveGroupByValue;
+//     return [...elements, this.removeSegment];
+//   }
+
+//   groupByChanged(segment, index) {
+//     if (segment.value === this.removeSegment.value) {
+//       this.groupBySegments.splice(index, 1);
+//     } else {
+//       segment.type = 'value';
+//     }
+
+//     const reducer = (memo, seg) => {
+//       if (!seg.fake) {
+//         memo.push(seg.value);
+//       }
+//       return memo;
+//     };
+
+//     this.target.aggregation.groupBys = this.groupBySegments.reduce(reducer, []);
+//     this.ensurePlusButton(this.groupBySegments);
+//     this.$rootScope.$broadcast('metricTypeChanged');
+//     this.$scope.refresh();
+//   }
+
+//   async getFilters(segment, index) {
+//     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 [];
+//     }
+
+//     const shortKey = filterKey.substring(filterKey.indexOf('.label.') + 7);
+
+//     if (filterKey.startsWith('metric.label.') && this.metricLabels.hasOwnProperty(shortKey)) {
+//       return this.metricLabels[shortKey];
+//     }
+
+//     if (filterKey.startsWith('resource.label.') && this.resourceLabels.hasOwnProperty(shortKey)) {
+//       return this.resourceLabels[shortKey];
+//     }
+
+//     if (filterKey === this.resourceTypeValue) {
+//       return this.resourceTypes;
+//     }
+
+//     return [];
+//   }
+
+//   filterSegmentUpdated(segment, index) {
+//     this.target.filters = this.filterSegments.filterSegmentUpdated(segment, index);
+//     this.$scope.refresh();
+//   }
+
+//   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());
+//     }
+//   }
+// }
+
+// coreModule.directive('stackdriverFilter', StackdriverFilter);
+// coreModule.controller('StackdriverFilterCtrl', StackdriverFilterCtrl);

+ 6 - 6
public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts

@@ -367,12 +367,12 @@ describe('StackdriverQueryFilterCtrl', () => {
 });
 
 function createCtrlWithFakes(existingFilters?: string[]) {
-  StackdriverFilterCtrl.prototype.loadMetricDescriptors = () => {
-    return Promise.resolve([]);
-  };
-  StackdriverFilterCtrl.prototype.getLabels = () => {
-    return Promise.resolve();
-  };
+  // StackdriverFilterCtrl.prototype.loadMetricDescriptors = () => {
+  //   return Promise.resolve([]);
+  // };
+  // StackdriverFilterCtrl.prototype.getLabels = () => {
+  //   return Promise.resolve();
+  // };
 
   const fakeSegmentServer = {
     newKey: val => {