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:
co-authored by
Diana Payton
parent
686149966a
commit
bb4b7381fc
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user