Przeglądaj źródła

reactify annotation query editor

Erik Sundell 7 lat temu
rodzic
commit
5ba429387f

+ 7 - 0
public/app/plugins/datasource/stackdriver/angular_wrappers.ts

@@ -1,5 +1,6 @@
 import { react2AngularDirective } from 'app/core/utils/react2angular';
 import { QueryEditor } from './components/QueryEditor';
+import { AnnotationQueryEditor } from './components/AnnotationQueryEditor';
 
 export function registerAngularDirectives() {
   react2AngularDirective('queryEditor', QueryEditor, [
@@ -9,4 +10,10 @@ export function registerAngularDirectives() {
     ['events', { watchDepth: 'reference' }],
     ['datasource', { watchDepth: 'reference' }],
   ]);
+  react2AngularDirective('annotationQueryEditor', AnnotationQueryEditor, [
+    'target',
+    'onQueryChange',
+    'onExecuteQuery',
+    ['datasource', { watchDepth: 'reference' }],
+  ]);
 }

+ 7 - 0
public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts

@@ -1,5 +1,6 @@
 import _ from 'lodash';
 import './query_filter_ctrl';
+import { registerAngularDirectives } from './angular_wrappers';
 
 export class StackdriverAnnotationsQueryCtrl {
   static templateUrl = 'partials/annotations.editor.html';
@@ -27,5 +28,11 @@ export class StackdriverAnnotationsQueryCtrl {
     this.annotation.target = this.annotation.target || {};
     this.annotation.target.refId = 'annotationQuery';
     _.defaultsDeep(this.annotation.target, this.defaults);
+    registerAngularDirectives();
+    this.handleQueryChange = this.handleQueryChange.bind(this);
+  }
+
+  handleQueryChange(target) {
+    Object.assign(this.annotation.target, target);
   }
 }

+ 108 - 0
public/app/plugins/datasource/stackdriver/components/AnnotationQueryEditor.tsx

@@ -0,0 +1,108 @@
+import React from 'react';
+import _ from 'lodash';
+
+import { Metrics } from './Metrics';
+import { Filter } from './Filter';
+import { AnnotationTarget } from '../types';
+
+export interface Props {
+  onQueryChange: (target: AnnotationTarget) => void;
+  target: AnnotationTarget;
+  datasource: any;
+}
+
+interface State extends AnnotationTarget {
+  [key: string]: any;
+}
+
+const DefaultTarget: State = {
+  defaultProject: 'loading project...',
+  metricType: '',
+  filters: [],
+  metricKind: '',
+  valueType: '',
+  refId: 'annotationQuery',
+  title: '',
+  text: '',
+};
+
+export class AnnotationQueryEditor extends React.Component<Props, State> {
+  state: State = DefaultTarget;
+
+  componentDidMount() {
+    this.setState({
+      ...this.props.target,
+    });
+  }
+
+  handleMetricTypeChange({ valueType, metricKind, type, unit }) {
+    const { onQueryChange } = this.props;
+    this.setState(
+      {
+        metricType: type,
+        unit,
+        valueType,
+        metricKind,
+      },
+      () => {
+        onQueryChange(this.state);
+      }
+    );
+  }
+
+  handleChange(prop, value) {
+    this.setState({ [prop]: value }, () => {
+      this.props.onQueryChange(this.state);
+    });
+  }
+
+  render() {
+    const { defaultProject, metricType, filters, refId, title } = this.state;
+    const { datasource } = this.props;
+
+    return (
+      <React.Fragment>
+        <Metrics
+          defaultProject={defaultProject}
+          metricType={metricType}
+          templateSrv={datasource.templateSrv}
+          datasource={datasource}
+          onChange={value => this.handleMetricTypeChange(value)}
+        >
+          {metric => (
+            <React.Fragment>
+              <Filter
+                filtersChanged={value => this.handleChange('filters', value)}
+                filters={filters}
+                refId={refId}
+                hideGroupBys={true}
+                templateSrv={datasource.templateSrv}
+                datasource={datasource}
+                metricType={metric ? metric.type : ''}
+              />
+            </React.Fragment>
+          )}
+        </Metrics>
+        <div className="gf-form gf-form-inline">
+          <div className="gf-form">
+            <span className="gf-form-label query-keyword width-9">Title</span>
+            <input
+              type="text"
+              className="gf-form-input width-20"
+              value={title}
+              onChange={e => this.handleChange('title', e.target.value)}
+            />
+          </div>
+          <div className="gf-form">
+            <span className="gf-form-label query-keyword width-9">Text</span>
+            <input type="text" className="gf-form-input width-20" ng-model="ctrl.annotation.target.text" />
+          </div>
+          <div className="gf-form gf-form--grow">
+            <div className="gf-form-label gf-form-label--grow" />
+          </div>
+        </div>
+        {/* <Help datasource={datasource} rawQuery={lastQuery} lastQueryError={lastQueryError} /> */}
+      </React.Fragment>
+    );
+  }
+}

+ 26 - 9
public/app/plugins/datasource/stackdriver/components/Filter.tsx

@@ -2,17 +2,20 @@ import React from 'react';
 import _ from 'lodash';
 import appEvents from 'app/core/app_events';
 
-import { QueryMeta, Target } from '../types';
+import { QueryMeta } from '../types';
 import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
 import '../query_filter_ctrl';
 
 export interface Props {
   filtersChanged: (filters) => void;
-  groupBysChanged: (groupBys) => void;
+  groupBysChanged?: (groupBys) => void;
   metricType: string;
   templateSrv: any;
-  target: Target;
+  groupBys?: string[];
+  filters: string[];
   datasource: any;
+  refId: string;
+  hideGroupBys: boolean;
 }
 
 interface State {
@@ -20,6 +23,12 @@ interface State {
   loading: Promise<any>;
 }
 
+const defaultLabelData = {
+  metricLabels: {},
+  resourceLabels: {},
+  resourceTypes: [],
+};
+
 export class Filter extends React.Component<Props, State> {
   element: any;
   component: AngularComponent;
@@ -29,17 +38,20 @@ export class Filter extends React.Component<Props, State> {
       return;
     }
 
-    const { target, filtersChanged, groupBysChanged } = this.props;
+    const { groupBys, filters, filtersChanged, groupBysChanged, hideGroupBys } = this.props;
     const loader = getAngularLoader();
     const template = '<stackdriver-filter> </stackdriver-filter>';
 
     const scopeProps = {
-      loading: this.loadLabels.bind(this),
+      loading: null,
       labelData: null,
-      target,
+      groupBys,
+      filters,
       filtersChanged,
       groupBysChanged,
+      hideGroupBys,
     };
+    scopeProps.loading = this.loadLabels(scopeProps);
 
     this.component = loader.load(this.element, scopeProps, template);
   }
@@ -60,11 +72,16 @@ export class Filter extends React.Component<Props, State> {
   async loadLabels(scope) {
     return new Promise(async resolve => {
       try {
-        const { meta } = await this.props.datasource.getLabels(this.props.target.metricType, this.props.target.refId);
-        scope.labelData = meta;
+        if (!this.props.metricType) {
+          scope.labelData = defaultLabelData;
+        } else {
+          const { meta } = await this.props.datasource.getLabels(this.props.metricType, this.props.refId);
+          scope.labelData = meta;
+        }
         resolve();
       } catch (error) {
-        appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.props.target.metricType]);
+        appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.props.metricType]);
+        scope.labelData = defaultLabelData;
         resolve();
       }
     });

+ 16 - 9
public/app/plugins/datasource/stackdriver/components/Metrics.tsx

@@ -19,6 +19,7 @@ interface State {
   service: string;
   metric: string;
   metricDescriptor: any;
+  defaultProject: string;
 }
 
 export class Metrics extends React.Component<Props, State> {
@@ -29,6 +30,7 @@ export class Metrics extends React.Component<Props, State> {
     service: '',
     metric: '',
     metricDescriptor: null,
+    defaultProject: '',
   };
 
   constructor(props) {
@@ -36,19 +38,21 @@ export class Metrics extends React.Component<Props, State> {
   }
 
   componentDidMount() {
-    this.getCurrentProject()
-      .then(this.loadMetricDescriptors.bind(this))
-      .then(this.initializeServiceAndMetrics.bind(this));
+    this.setState({ defaultProject: this.props.defaultProject }, () => {
+      this.getCurrentProject()
+        .then(this.loadMetricDescriptors.bind(this))
+        .then(this.initializeServiceAndMetrics.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();
+        if (!this.state.defaultProject || this.state.defaultProject === 'loading project...') {
+          const defaultProject = await this.props.datasource.getDefaultProject();
+          this.setState({ defaultProject });
         }
-        resolve(this.props.defaultProject);
+        resolve(this.state.defaultProject);
       } catch (error) {
         // appEvents.emit('ds-request-error', error);
         reject();
@@ -57,8 +61,8 @@ export class Metrics extends React.Component<Props, State> {
   }
 
   async loadMetricDescriptors() {
-    if (this.props.defaultProject !== 'loading project...') {
-      const metricDescriptors = await this.props.datasource.getMetricTypes(this.props.defaultProject);
+    if (this.state.defaultProject !== 'loading project...') {
+      const metricDescriptors = await this.props.datasource.getMetricTypes(this.state.defaultProject);
       this.setState({ metricDescriptors });
       return metricDescriptors;
     } else {
@@ -81,6 +85,9 @@ export class Metrics extends React.Component<Props, State> {
 
   getMetricsList(metricDescriptors) {
     const selectedMetricDescriptor = this.getSelectedMetricDescriptor(this.props.metricType);
+    if (!selectedMetricDescriptor) {
+      return [];
+    }
     const metricsByService = metricDescriptors.filter(m => m.service === selectedMetricDescriptor.service).map(m => ({
       service: m.service,
       value: m.type,

+ 11 - 3
public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx

@@ -60,6 +60,11 @@ export class QueryEditor extends React.Component<Props, State> {
     });
   }
 
+  componentWillUnmount() {
+    this.props.events.off('data-received');
+    this.props.events.off('data-error');
+  }
+
   onDataReceived(dataList) {
     const series = dataList.find(item => item.refId === this.props.target.refId);
     if (series) {
@@ -121,12 +126,14 @@ export class QueryEditor extends React.Component<Props, State> {
       metricType,
       crossSeriesReducer,
       groupBys,
+      filters,
       perSeriesAligner,
       alignOptions,
       alignmentPeriod,
       aliasBy,
       lastQuery,
       lastQueryError,
+      refId,
     } = this.state;
     const { datasource } = this.props;
 
@@ -144,7 +151,10 @@ export class QueryEditor extends React.Component<Props, State> {
               <Filter
                 filtersChanged={value => this.handleChange('filters', value)}
                 groupBysChanged={value => this.handleChange('groupBys', value)}
-                target={this.state}
+                filters={filters}
+                groupBys={groupBys}
+                refId={refId}
+                hideGroupBys={false}
                 templateSrv={datasource.templateSrv}
                 datasource={datasource}
                 metricType={metric ? metric.type : ''}
@@ -168,13 +178,11 @@ export class QueryEditor extends React.Component<Props, State> {
                 }
               </Aggregations>
               <AliasBy value={aliasBy} onChange={value => this.handleChange('aliasBy', value)} />
-
               <AlignmentPeriods
                 templateSrv={datasource.templateSrv}
                 alignmentPeriod={alignmentPeriod}
                 onChange={value => this.handleChange('alignmentPeriod', value)}
               />
-
               <Help datasource={datasource} rawQuery={lastQuery} lastQueryError={lastQueryError} />
             </React.Fragment>
           )}

+ 2 - 2
public/app/plugins/datasource/stackdriver/filter_segments.ts

@@ -5,13 +5,13 @@ export class FilterSegments {
   filterSegments: any[];
   removeSegment: any;
 
-  constructor(private uiSegmentSrv, private target, private getFilterKeysFunc, private getFilterValuesFunc) {}
+  constructor(private uiSegmentSrv, private filters, private getFilterKeysFunc, private getFilterValuesFunc) {}
 
   buildSegmentModel() {
     this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: DefaultRemoveFilterValue });
 
     this.filterSegments = [];
-    this.target.filters.forEach((f, index) => {
+    this.filters.forEach((f, index) => {
       switch (index % 4) {
         case 0:
           this.filterSegments.push(this.uiSegmentSrv.newKey(f));

+ 10 - 7
public/app/plugins/datasource/stackdriver/partials/annotations.editor.html

@@ -1,7 +1,12 @@
-<stackdriver-filter target="ctrl.annotation.target" refresh="ctrl.refresh()" datasource="ctrl.datasource"
-  default-dropdown-value="ctrl.defaultDropdownValue" default-service-value="ctrl.defaultServiceValue" hide-group-bys="true"></stackdriver-filter>
+<!-- <stackdriver-filter target="ctrl.annotation.target" refresh="ctrl.refresh()" datasource="ctrl.datasource"
+  default-dropdown-value="ctrl.defaultDropdownValue" default-service-value="ctrl.defaultServiceValue" hide-group-bys="true"></stackdriver-filter> -->
+<annotation-query-editor
+  target="ctrl.annotation.target"
+  datasource="ctrl.datasource"
+  on-query-change="(ctrl.handleQueryChange)"
+></annotation-query-editor>
 
-<div class="gf-form gf-form-inline">
+<!-- <div class="gf-form gf-form-inline">
   <div class="gf-form">
     <span class="gf-form-label query-keyword width-9">Title</span>
     <input type="text" class="gf-form-input width-20" ng-model="ctrl.annotation.target.title" />
@@ -10,10 +15,8 @@
     <span class="gf-form-label query-keyword width-9">Text</span>
     <input type="text" class="gf-form-input width-20" ng-model="ctrl.annotation.target.text" />
   </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 grafana-info-box" style="padding: 0">
   <pre class="gf-form-pre alert alert-info" style="margin-right: 0"><h5>Annotation Query Format</h5>

+ 7 - 7
public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts

@@ -13,7 +13,8 @@ export class StackdriverFilter {
       scope: {
         labelData: '<',
         loading: '<',
-        target: '=',
+        groupBys: '<',
+        filters: '<',
         filtersChanged: '&',
         groupBysChanged: '&',
         hideGroupBys: '<',
@@ -28,19 +29,17 @@ export class StackdriverFilterCtrl {
   groupBySegments: any[];
   filterSegments: FilterSegments;
   removeSegment: any;
-  target: any;
 
   /** @ngInject */
   constructor(private $scope, private uiSegmentSrv, private templateSrv, private $rootScope) {
     this.$scope = $scope.$parent;
-    this.target = this.$scope.target;
 
     this.initSegments(this.$scope.hideGroupBys);
   }
 
   initSegments(hideGroupBys: boolean) {
     if (!hideGroupBys) {
-      this.groupBySegments = this.target.groupBys.map(groupBy => {
+      this.groupBySegments = this.$scope.groupBys.map(groupBy => {
         return this.uiSegmentSrv.getSegmentForValue(groupBy);
       });
       this.ensurePlusButton(this.groupBySegments);
@@ -50,7 +49,7 @@ export class StackdriverFilterCtrl {
 
     this.filterSegments = new FilterSegments(
       this.uiSegmentSrv,
-      this.target,
+      this.$scope.filters,
       this.getFilterKeys.bind(this),
       this.getFilterValues.bind(this)
     );
@@ -93,7 +92,7 @@ export class StackdriverFilterCtrl {
   async getFilterKeys(segment, removeText?: string) {
     let elements = await this.createLabelKeyElements();
 
-    if (this.target.filters.indexOf(this.resourceTypeValue) !== -1) {
+    if (this.$scope.filters.indexOf(this.resourceTypeValue) !== -1) {
       elements = elements.filter(e => e.value !== this.resourceTypeValue);
     }
 
@@ -111,7 +110,7 @@ export class StackdriverFilterCtrl {
   async getGroupBys(segment) {
     let elements = await this.createLabelKeyElements();
 
-    elements = elements.filter(e => this.target.groupBys.indexOf(e.value) === -1);
+    elements = elements.filter(e => this.$scope.groupBys.indexOf(e.value) === -1);
     const noValueOrPlusButton = !segment || segment.type === 'plus-button';
     if (noValueOrPlusButton && elements.length === 0) {
       return [];
@@ -142,6 +141,7 @@ export class StackdriverFilterCtrl {
   }
 
   async getFilters(segment, index) {
+    await this.$scope.loading;
     const hasNoFilterKeys =
       this.$scope.labelData.metricLabels && Object.keys(this.$scope.labelData.metricLabels).length === 0;
     return this.filterSegments.getFilters(segment, index, hasNoFilterKeys);

+ 13 - 2
public/app/plugins/datasource/stackdriver/types.ts

@@ -32,8 +32,19 @@ export interface Target {
   groupBys: string[];
   filters: string[];
   aliasBy: string;
-  metricKind: any;
-  valueType: any;
+  metricKind: string;
+  valueType: string;
+}
+
+export interface AnnotationTarget {
+  defaultProject: string;
+  metricType: string;
+  refId: string;
+  filters: string[];
+  metricKind: string;
+  valueType: string;
+  title: string;
+  text: string;
 }
 
 export interface QueryMeta {