Browse Source

stackdriver: wip annotation support

Daniel Lee 7 years ago
parent
commit
03b43ab769

+ 76 - 4
pkg/tsdb/stackdriver/annotation_query.go

@@ -2,6 +2,7 @@ package stackdriver
 
 import (
 	"context"
+	"time"
 
 	"github.com/grafana/grafana/pkg/tsdb"
 )
@@ -11,14 +12,85 @@ func (e *StackdriverExecutor) executeAnnotationQuery(ctx context.Context, tsdbQu
 		Results: make(map[string]*tsdb.QueryResult),
 	}
 
-	_, err := e.buildAnnotationQuery(tsdbQuery)
+	firstQuery := tsdbQuery.Queries[0]
+
+	queries, err := e.buildQueries(tsdbQuery)
+	if err != nil {
+		return nil, err
+	}
+
+	queryRes, resp, err := e.executeQuery(ctx, queries[0], tsdbQuery)
 	if err != nil {
 		return nil, err
 	}
+	title := firstQuery.Model.Get("title").MustString()
+	text := firstQuery.Model.Get("text").MustString()
+	tags := firstQuery.Model.Get("tags").MustString()
+	err = e.parseToAnnotations(queryRes, resp, queries[0], title, text, tags)
+	result.Results[firstQuery.RefId] = queryRes
 
-	return result, nil
+	return result, err
 }
 
-func (e *StackdriverExecutor) buildAnnotationQuery(tsdbQuery *tsdb.TsdbQuery) (*StackdriverQuery, error) {
-	return &StackdriverQuery{}, nil
+func (e *StackdriverExecutor) parseToAnnotations(queryRes *tsdb.QueryResult, data StackdriverResponse, query *StackdriverQuery, title string, text string, tags string) error {
+	annotations := make([]map[string]string, 0)
+
+	for _, series := range data.TimeSeries {
+		// reverse the order to be ascending
+		for i := len(series.Points) - 1; i >= 0; i-- {
+			point := series.Points[i]
+
+			annotation := make(map[string]string)
+			annotation["time"] = point.Interval.EndTime.UTC().Format(time.RFC3339)
+			annotation["title"] = title
+			annotation["tags"] = tags
+			annotation["text"] = text
+			annotations = append(annotations, annotation)
+		}
+	}
+
+	transformAnnotationToTable(annotations, queryRes)
+	return nil
 }
+
+func transformAnnotationToTable(data []map[string]string, result *tsdb.QueryResult) {
+	table := &tsdb.Table{
+		Columns: make([]tsdb.TableColumn, 4),
+		Rows:    make([]tsdb.RowValues, 0),
+	}
+	table.Columns[0].Text = "time"
+	table.Columns[1].Text = "title"
+	table.Columns[2].Text = "tags"
+	table.Columns[3].Text = "text"
+
+	for _, r := range data {
+		values := make([]interface{}, 4)
+		values[0] = r["time"]
+		values[1] = r["title"]
+		values[2] = r["tags"]
+		values[3] = r["text"]
+		table.Rows = append(table.Rows, values)
+	}
+	result.Tables = append(result.Tables, table)
+	result.Meta.Set("rowCount", len(data))
+	slog.Info("anno", "len", len(data))
+}
+
+// func (e *StackdriverExecutor) buildAnnotationQuery(tsdbQuery *tsdb.TsdbQuery) (*StackdriverQuery, error) {
+// 	firstQuery := queryContext.Queries[0]
+
+// 	metricType := query.Model.Get("metricType").MustString()
+// 	filterParts := query.Model.Get("filters").MustArray()
+// 	filterString := buildFilterString(metricType, filterParts)
+// 	params := url.Values{}
+// 	params.Add("interval.startTime", startTime.UTC().Format(time.RFC3339))
+// 	params.Add("interval.endTime", endTime.UTC().Format(time.RFC3339))
+// 	params.Add("filter", buildFilterString(metricType, filterParts))
+// 	params.Add("view", "FULL")
+
+// 	return &StackdriverQuery{
+// 		RefID:  firstQuery.RefID,
+// 		Params: params,
+// 		Target: "",
+// 	}, nil
+// }

+ 14 - 22
pkg/tsdb/stackdriver/annotation_query_test.go

@@ -1,9 +1,7 @@
 package stackdriver
 
 import (
-	"fmt"
 	"testing"
-	"time"
 
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/tsdb"
@@ -14,28 +12,22 @@ import (
 func TestStackdriverAnnotationQuery(t *testing.T) {
 	Convey("Stackdriver Annotation Query Executor", t, func() {
 		executor := &StackdriverExecutor{}
-		Convey("Parse queries from frontend and build Stackdriver API queries", func() {
-			fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
-			tsdbQuery := &tsdb.TsdbQuery{
-				TimeRange: &tsdb.TimeRange{
-					From: fmt.Sprintf("%v", fromStart.Unix()*1000),
-					To:   fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
-				},
-				Queries: []*tsdb.Query{
-					{
-						Model: simplejson.NewFromAny(map[string]interface{}{
-							"metricType": "a/metric/type",
-							"view":       "FULL",
-							"type":       "annotationQuery",
-						}),
-						RefId: "annotationQuery",
-					},
-				},
-			}
-			query, err := executor.buildAnnotationQuery(tsdbQuery)
+		Convey("When parsing the stackdriver api response", func() {
+			data, err := loadTestFile("./test-data/2-series-response-no-agg.json")
 			So(err, ShouldBeNil)
+			So(len(data.TimeSeries), ShouldEqual, 3)
 
-			So(query, ShouldNotBeNil)
+			res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "annotationQuery"}
+			query := &StackdriverQuery{}
+			err = executor.parseToAnnotations(res, data, query, "atitle", "atext", "atag")
+			So(err, ShouldBeNil)
+
+			Convey("Should return annotations table", func() {
+				So(len(res.Tables), ShouldEqual, 1)
+				So(len(res.Tables[0].Rows), ShouldEqual, 9)
+				So(res.Tables[0].Rows[0][1], ShouldEqual, "atitle")
+				So(res.Tables[0].Rows[0][3], ShouldEqual, "atext")
+			})
 		})
 	})
 }

+ 11 - 13
pkg/tsdb/stackdriver/stackdriver.go

@@ -93,10 +93,14 @@ func (e *StackdriverExecutor) executeTimeSeriesQuery(ctx context.Context, tsdbQu
 	}
 
 	for _, query := range queries {
-		queryRes, err := e.executeQuery(ctx, query, tsdbQuery)
+		queryRes, resp, err := e.executeQuery(ctx, query, tsdbQuery)
 		if err != nil {
 			return nil, err
 		}
+		err = e.parseResponse(queryRes, resp, query)
+		if err != nil {
+			queryRes.Error = err
+		}
 		result.Results[query.RefID] = queryRes
 	}
 
@@ -219,13 +223,13 @@ func setAggParams(params *url.Values, query *tsdb.Query, durationSeconds int) {
 	}
 }
 
-func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *StackdriverQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, error) {
+func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *StackdriverQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, StackdriverResponse, error) {
 	queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID}
 
 	req, err := e.createRequest(ctx, e.dsInfo)
 	if err != nil {
 		queryResult.Error = err
-		return queryResult, nil
+		return queryResult, StackdriverResponse{}, nil
 	}
 
 	req.URL.RawQuery = query.Params.Encode()
@@ -257,22 +261,16 @@ func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *Stackdriv
 	res, err := ctxhttp.Do(ctx, e.httpClient, req)
 	if err != nil {
 		queryResult.Error = err
-		return queryResult, nil
+		return queryResult, StackdriverResponse{}, nil
 	}
 
 	data, err := e.unmarshalResponse(res)
 	if err != nil {
 		queryResult.Error = err
-		return queryResult, nil
-	}
-
-	err = e.parseResponse(queryResult, data, query)
-	if err != nil {
-		queryResult.Error = err
-		return queryResult, nil
+		return queryResult, StackdriverResponse{}, nil
 	}
 
-	return queryResult, nil
+	return queryResult, data, nil
 }
 
 func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackdriverResponse, error) {
@@ -429,7 +427,7 @@ func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.
 
 	req, err := http.NewRequest(http.MethodGet, "https://monitoring.googleapis.com/", nil)
 	if err != nil {
-		slog.Info("Failed to create request", "error", err)
+		slog.Error("Failed to create request", "error", err)
 		return nil, fmt.Errorf("Failed to create request. error: %v", err)
 	}
 

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

@@ -0,0 +1,32 @@
+import _ from 'lodash';
+
+import './query_filter_ctrl';
+
+export class StackdriverAnnotationsQueryCtrl {
+  static templateUrl = 'partials/annotations.editor.html';
+  annotation: any;
+  datasource: any;
+
+  defaultDropdownValue = 'Select Metric';
+  defaultServiceValue = 'All Services';
+
+  defaults = {
+    project: {
+      id: 'default',
+      name: 'loading project...',
+    },
+    metricType: this.defaultDropdownValue,
+    metricService: this.defaultServiceValue,
+    metric: '',
+    filters: [],
+    metricKind: '',
+    valueType: '',
+  };
+
+  /** @ngInject */
+  constructor() {
+    this.annotation.target = this.annotation.target || {};
+    this.annotation.target.refId = 'annotationQuery';
+    _.defaultsDeep(this.annotation.target, this.defaults);
+  }
+}

+ 2 - 5
public/app/plugins/datasource/stackdriver/module.ts

@@ -1,14 +1,11 @@
 import StackdriverDatasource from './datasource';
 import { StackdriverQueryCtrl } from './query_ctrl';
 import { StackdriverConfigCtrl } from './config_ctrl';
-
-// class AnnotationsQueryCtrl {
-//   static templateUrl = 'partials/annotations.editor.html';
-// }
+import { StackdriverAnnotationsQueryCtrl } from './annotations_query_ctrl';
 
 export {
   StackdriverDatasource as Datasource,
   StackdriverQueryCtrl as QueryCtrl,
   StackdriverConfigCtrl as ConfigCtrl,
-  // AnnotationsQueryCtrl,
+  StackdriverAnnotationsQueryCtrl as AnnotationsQueryCtrl,
 };

+ 11 - 8
public/app/plugins/datasource/stackdriver/partials/annotations.editor.html

@@ -1,13 +1,16 @@
-<div class="gf-form-group">
+<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>
+
+<div class="gf-form-inline">
   <div class="gf-form">
-    <span class="gf-form-label width-12">Graphite query</span>
-    <input type="text" class="gf-form-input" ng-model='ctrl.annotation.target' placeholder="Example: statsd.application.counters.*.count"></input>
+    <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" />
   </div>
-
-	<h5 class="section-heading">Or</h5>
-
   <div class="gf-form">
-    <span class="gf-form-label width-12">Graphite events tags</span>
-    <input type="text" class="gf-form-input" ng-model='ctrl.annotation.tags' placeholder="Example: event_tag_name"></input>
+    <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>

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

@@ -1,5 +1,6 @@
 <query-editor-row query-ctrl="ctrl" has-text-edit-mode="false">
-  <stackdriver-filter  target="ctrl.target" refresh="ctrl.refresh()" datasource="ctrl.datasource" default-dropdown-value="ctrl.defaultDropdownValue" default-service-value="ctrl.defaultServiceValue"></stackdriver-filter>
+  <stackdriver-filter target="ctrl.target" refresh="ctrl.refresh()" datasource="ctrl.datasource" default-dropdown-value="ctrl.defaultDropdownValue"
+    default-service-value="ctrl.defaultServiceValue"></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">

+ 1 - 6
public/app/plugins/datasource/stackdriver/partials/query.filter.html

@@ -4,11 +4,6 @@
     <gf-form-dropdown model="ctrl.service" get-options="ctrl.services" class="min-width-20" disabled type="text"
       allow-custom="true" lookup-text="true" css-class="min-width-12" on-change="ctrl.onServiceChange(ctrl.service)"></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 width-9">Metric</span>
     <gf-form-dropdown model="ctrl.metricType" get-options="ctrl.metrics" class="min-width-20" disabled type="text"
@@ -29,7 +24,7 @@
     <div class="gf-form-label gf-form-label--grow"></div>
   </div>
 </div>
-<div class="gf-form-inline">
+<div class="gf-form-inline" ng-hide="ctrl.$scope.hideGroupBys">
   <div class="gf-form">
     <span class="gf-form-label query-keyword width-9">Group By</span>
     <div class="gf-form" ng-repeat="segment in ctrl.groupBySegments">

+ 1 - 1
public/app/plugins/datasource/stackdriver/plugin.json

@@ -4,7 +4,7 @@
   "id": "stackdriver",
   "metrics": true,
   "alerting": true,
-  "annotations": false,
+  "annotations": true,
   "state": "beta",
   "queryOptions": {
     "maxDataPoints": true,

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

@@ -16,6 +16,7 @@ export class StackdriverFilter {
         refresh: '&',
         defaultDropdownValue: '<',
         defaultServiceValue: '<',
+        hideGroupBys: '<',
       },
     };
   }
@@ -54,15 +55,18 @@ export class StackdriverFilterCtrl {
       .then(this.loadMetricDescriptors.bind(this))
       .then(this.getLabels.bind(this));
 
-    this.initSegments();
+    this.initSegments($scope.hideGroupBys);
   }
 
-  initSegments() {
-    this.groupBySegments = this.target.aggregation.groupBys.map(groupBy => {
-      return this.uiSegmentSrv.getSegmentForValue(groupBy);
-    });
+  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.ensurePlusButton(this.groupBySegments);
 
     this.filterSegments = new FilterSegments(
       this.uiSegmentSrv,
@@ -142,7 +146,11 @@ export class StackdriverFilterCtrl {
         this.resourceLabels = data.results[this.target.refId].meta.resourceLabels;
         resolve();
       } catch (error) {
-        console.log(error.data.message);
+        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();
       }