diff --git a/pkg/tsdb/stackdriver/annotation_query.go b/pkg/tsdb/stackdriver/annotation_query.go index 37e68a6d3bd..b8758eeb378 100644 --- a/pkg/tsdb/stackdriver/annotation_query.go +++ b/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 } - return result, nil + 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, 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 +// } diff --git a/pkg/tsdb/stackdriver/annotation_query_test.go b/pkg/tsdb/stackdriver/annotation_query_test.go index 0fb47fbe453..16c0897fc05 100644 --- a/pkg/tsdb/stackdriver/annotation_query_test.go +++ b/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) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "annotationQuery"} + query := &StackdriverQuery{} + err = executor.parseToAnnotations(res, data, query, "atitle", "atext", "atag") So(err, ShouldBeNil) - So(query, ShouldNotBeNil) + 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") + }) }) }) } diff --git a/pkg/tsdb/stackdriver/stackdriver.go b/pkg/tsdb/stackdriver/stackdriver.go index b854e590ab0..847fb271c01 100644 --- a/pkg/tsdb/stackdriver/stackdriver.go +++ b/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 + return queryResult, StackdriverResponse{}, nil } - err = e.parseResponse(queryResult, data, query) - if err != nil { - queryResult.Error = err - return queryResult, 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) } diff --git a/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts b/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts new file mode 100644 index 00000000000..407707ed779 --- /dev/null +++ b/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); + } +} diff --git a/public/app/plugins/datasource/stackdriver/module.ts b/public/app/plugins/datasource/stackdriver/module.ts index eba3bcfb950..183c5c9ff88 100644 --- a/public/app/plugins/datasource/stackdriver/module.ts +++ b/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, }; diff --git a/public/app/plugins/datasource/stackdriver/partials/annotations.editor.html b/public/app/plugins/datasource/stackdriver/partials/annotations.editor.html index 9d228b8e4f9..592dffacd51 100644 --- a/public/app/plugins/datasource/stackdriver/partials/annotations.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/annotations.editor.html @@ -1,13 +1,16 @@ -