Stackdriver: Deep linking from Grafana panels to the Metrics Explorer (#25858)

* Add stackdriver deep link

* No deep link for SLO queries

* Add tests

* Update docs/sources/features/datasources/stackdriver.md

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* Enforce resource type

* Navigate to google account chooser first

* Change renamed image reference

Fix misspelling in image name and change it to conform with the rest cloud monitoring images

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
This commit is contained in:
Sofia Papagiannaki
2020-07-01 01:16:20 +03:00
committed by GitHub
co-authored by Diana Payton
parent 686149966a
commit bb4b7381fc
4 changed files with 400 additions and 6 deletions
@@ -5,6 +5,8 @@ import (
"fmt"
"io/ioutil"
"math"
"net/url"
"reflect"
"strconv"
"testing"
"time"
@@ -53,18 +55,57 @@ func TestCloudMonitoring(t *testing.T) {
So(queries[0].Params["filter"][0], ShouldEqual, "metric.type=\"a/metric/type\"")
So(queries[0].Params["view"][0], ShouldEqual, "FULL")
So(queries[0].AliasBy, ShouldEqual, "testalias")
Convey("and generated deep link has correct parameters", func() {
dl := queries[0].buildDeepLink()
So(dl, ShouldBeEmpty) // no resource type found
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl = queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-03-15T13:00:00Z",
"end": "2018-03-15T13:34:00Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"perSeriesAligner": "ALIGN_MEAN",
"filter": "resource.type=\"a/resource/type\" metric.type=\"a/metric/type\"",
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
Convey("and query has filters", func() {
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
"metricType": "a/metric/type",
"filters": []interface{}{"key", "=", "value", "AND", "key2", "=", "value2"},
"filters": []interface{}{"key", "=", "value", "AND", "key2", "=", "value2", "AND", "resource.type", "=", "another/resource/type"},
})
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(len(queries), ShouldEqual, 1)
So(queries[0].Params["filter"][0], ShouldEqual, `metric.type="a/metric/type" key="value" key2="value2"`)
So(queries[0].Params["filter"][0], ShouldEqual, `metric.type="a/metric/type" key="value" key2="value2" resource.type="another/resource/type"`)
Convey("and generated deep link has correct parameters", func() {
// assign a resource type to query parameters
// in the actual workflow this information comes from the response of the Monitoring API
// the deep link should not contain this resource type since another resource type is included in the query filters
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-03-15T13:00:00Z",
"end": "2018-03-15T13:34:00Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"filter": `metric.type="a/metric/type" key="value" key2="value2" resource.type="another/resource/type"`,
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
Convey("and alignmentPeriod is set to grafana-auto", func() {
@@ -78,6 +119,23 @@ func TestCloudMonitoring(t *testing.T) {
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, `+1000s`)
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-03-15T13:00:00Z",
"end": "2018-03-15T13:34:00Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `1000s`,
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
Convey("and IntervalMs is less than 60000", func() {
tsdbQuery.Queries[0].IntervalMs = 30000
@@ -89,6 +147,23 @@ func TestCloudMonitoring(t *testing.T) {
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, `+60s`)
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-03-15T13:00:00Z",
"end": "2018-03-15T13:34:00Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `60s`,
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
})
@@ -158,6 +233,23 @@ func TestCloudMonitoring(t *testing.T) {
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, `+60s`)
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-09-27T07:28:42Z",
"end": "2018-09-27T09:28:42Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `60s`,
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
Convey("and range is 22 hours", func() {
@@ -171,6 +263,23 @@ func TestCloudMonitoring(t *testing.T) {
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, `+60s`)
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-09-27T07:48:44Z",
"end": "2018-09-28T05:48:44Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `60s`,
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
Convey("and range is 23 hours", func() {
@@ -184,6 +293,23 @@ func TestCloudMonitoring(t *testing.T) {
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, `+300s`)
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-09-27T07:49:27Z",
"end": "2018-09-28T06:49:27Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `300s`,
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
Convey("and range is 7 days", func() {
@@ -197,6 +323,23 @@ func TestCloudMonitoring(t *testing.T) {
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, `+3600s`)
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-09-27T08:18:44Z",
"end": "2018-10-04T08:18:44Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `3600s`,
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
})
@@ -210,6 +353,23 @@ func TestCloudMonitoring(t *testing.T) {
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, `+600s`)
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-03-15T13:00:00Z",
"end": "2018-03-15T13:34:00Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `600s`,
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
})
@@ -234,6 +394,26 @@ func TestCloudMonitoring(t *testing.T) {
So(queries[0].Params["aggregation.alignmentPeriod"][0], ShouldEqual, "+60s")
So(queries[0].Params["filter"][0], ShouldEqual, "metric.type=\"a/metric/type\"")
So(queries[0].Params["view"][0], ShouldEqual, "FULL")
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-03-15T13:00:00Z",
"end": "2018-03-15T13:34:00Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `60s`,
"crossSeriesReducer": "REDUCE_SUM",
"perSeriesAligner": "ALIGN_MEAN",
"filter": "resource.type=\"a/resource/type\" metric.type=\"a/metric/type\"",
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
Convey("and query has group bys", func() {
@@ -258,6 +438,26 @@ func TestCloudMonitoring(t *testing.T) {
So(queries[0].Params["aggregation.groupByFields"][1], ShouldEqual, "metric.label.group2")
So(queries[0].Params["filter"][0], ShouldEqual, "metric.type=\"a/metric/type\"")
So(queries[0].Params["view"][0], ShouldEqual, "FULL")
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-03-15T13:00:00Z",
"end": "2018-03-15T13:34:00Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `60s`,
"perSeriesAligner": "ALIGN_MEAN",
"filter": "resource.type=\"a/resource/type\" metric.type=\"a/metric/type\"",
"groupByFields": []interface{}{"metric.label.group1", "metric.label.group2"},
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
})
@@ -303,6 +503,26 @@ func TestCloudMonitoring(t *testing.T) {
So(queries[0].Params["view"][0], ShouldEqual, "FULL")
So(queries[0].AliasBy, ShouldEqual, "testalias")
So(queries[0].GroupBys, ShouldResemble, []string{"metric.label.group1", "metric.label.group2"})
Convey("and generated deep link has correct parameters", func() {
// assign resource type to query parameters to be included in the deep link filter
// in the actual workflow this information comes from the response of the Monitoring API
queries[0].Params.Set("resourceType", "a/resource/type")
dl := queries[0].buildDeepLink()
expectedTimeSelection := map[string]string{
"timeRange": "custom",
"start": "2018-03-15T13:00:00Z",
"end": "2018-03-15T13:34:00Z",
}
expectedTimeSeriesFilter := map[string]interface{}{
"minAlignmentPeriod": `60s`,
"perSeriesAligner": "ALIGN_MEAN",
"filter": "resource.type=\"a/resource/type\" metric.type=\"a/metric/type\"",
"groupByFields": []interface{}{"metric.label.group1", "metric.label.group2"},
}
verifyDeepLink(dl, expectedTimeSelection, expectedTimeSeriesFilter)
})
})
Convey("and query type is SLOs", func() {
@@ -351,6 +571,11 @@ func TestCloudMonitoring(t *testing.T) {
queries, err := executor.buildQueries(tsdbQuery)
So(err, ShouldBeNil)
So(queries[0].Params["aggregation.perSeriesAligner"][0], ShouldEqual, "ALIGN_NEXT_OLDER")
Convey("and empty deep link", func() {
dl := queries[0].buildDeepLink()
So(dl, ShouldBeEmpty)
})
})
})
})
@@ -774,3 +999,58 @@ func loadTestFile(path string) (cloudMonitoringResponse, error) {
err = json.Unmarshal(jsonBody, &data)
return data, err
}
func verifyDeepLink(dl string, expectedTimeSelection map[string]string, expectedTimeSeriesFilter map[string]interface{}) {
u, err := url.Parse(dl)
So(err, ShouldBeNil)
So(u.Scheme, ShouldEqual, "https")
So(u.Host, ShouldEqual, "accounts.google.com")
So(u.Path, ShouldEqual, "/AccountChooser")
params, err := url.ParseQuery(u.RawQuery)
So(err, ShouldBeNil)
continueParam := params.Get("continue")
So(continueParam, ShouldNotBeEmpty)
u, err = url.Parse(continueParam)
So(err, ShouldBeNil)
params, err = url.ParseQuery(u.RawQuery)
So(err, ShouldBeNil)
pageStateStr := params.Get("pageState")
So(pageStateStr, ShouldNotBeEmpty)
var pageState map[string]map[string]interface{}
err = json.Unmarshal([]byte(pageStateStr), &pageState)
So(err, ShouldBeNil)
timeSelection, ok := pageState["timeSelection"]
So(ok, ShouldBeTrue)
for k, v := range expectedTimeSelection {
s, ok := timeSelection[k].(string)
So(ok, ShouldBeTrue)
So(s, ShouldEqual, v)
}
dataSets, ok := pageState["xyChart"]["dataSets"].([]interface{})
So(ok, ShouldBeTrue)
So(len(dataSets), ShouldEqual, 1)
dataSet, ok := dataSets[0].(map[string]interface{})
So(ok, ShouldBeTrue)
i, ok := dataSet["timeSeriesFilter"]
So(ok, ShouldBeTrue)
timeSeriesFilter := i.(map[string]interface{})
for k, v := range expectedTimeSeriesFilter {
s, ok := timeSeriesFilter[k]
So(ok, ShouldBeTrue)
rt := reflect.TypeOf(v)
switch rt.Kind() {
case reflect.Slice, reflect.Array:
So(s, ShouldResemble, v)
default:
So(s, ShouldEqual, v)
}
}
}