mirror of
https://github.com/grafana/grafana.git
synced 2025-01-27 16:57:14 -06:00
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:
parent
686149966a
commit
bb4b7381fc
@ -324,3 +324,14 @@ datasources:
|
||||
jsonData:
|
||||
authenticationType: gce
|
||||
```
|
||||
|
||||
## Deep linking from Grafana panels to the Metrics Explorer in Google Cloud Console
|
||||
|
||||
Only available in Grafana v7.1+.
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v71/cloudmonitoring_deep_linking.png" max-width="500px" class="docs-image--right" caption="Google Cloud Monitoring deep linking" >}}
|
||||
|
||||
> **Note:** This feature is only available for Metric queries.
|
||||
|
||||
Click on a time series in the panel to see a context menu with a link to View in Metrics Explorer in Google Cloud Console. Clicking that link opens the Metrics Explorer in the Google Cloud Console and runs the query from the Grafana panel there.
|
||||
The link navigates the user first to the Google Account Chooser and after successfully selecting an account, the user is redirected to the Metrics Explorer. The provided link is valid for any account, but it only displays the query if your account has access to the GCP project specified in the query.
|
||||
|
@ -112,6 +112,85 @@ func (e *CloudMonitoringExecutor) getGCEDefaultProject(ctx context.Context, tsdb
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (query *cloudMonitoringQuery) isSLO() bool {
|
||||
return query.Slo != ""
|
||||
}
|
||||
|
||||
func (query *cloudMonitoringQuery) buildDeepLink() string {
|
||||
if query.isSLO() {
|
||||
return ""
|
||||
}
|
||||
|
||||
filter := query.Params.Get("filter")
|
||||
if !strings.Contains(filter, "resource.type=") {
|
||||
resourceType := query.Params.Get("resourceType")
|
||||
if resourceType == "" {
|
||||
slog.Error("Failed to generate deep link: no resource type found", "ProjectName", query.ProjectName, "query", query.RefID)
|
||||
return ""
|
||||
}
|
||||
filter = fmt.Sprintf(`resource.type="%s" %s`, resourceType, filter)
|
||||
}
|
||||
|
||||
u, err := url.Parse("https://console.cloud.google.com/monitoring/metrics-explorer")
|
||||
if err != nil {
|
||||
slog.Error("Failed to generate deep link: unable to parse metrics explorer URL", "ProjectName", query.ProjectName, "query", query.RefID)
|
||||
return ""
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
q.Set("project", query.ProjectName)
|
||||
|
||||
pageState := map[string]interface{}{
|
||||
"xyChart": map[string]interface{}{
|
||||
"constantLines": []string{},
|
||||
"dataSets": []map[string]interface{}{
|
||||
{
|
||||
"timeSeriesFilter": map[string]interface{}{
|
||||
"aggregations": []string{},
|
||||
"crossSeriesReducer": query.Params.Get("aggregation.crossSeriesReducer"),
|
||||
"filter": filter,
|
||||
"groupByFields": query.Params["aggregation.groupByFields"],
|
||||
"minAlignmentPeriod": strings.TrimPrefix(query.Params.Get("aggregation.alignmentPeriod"), "+"), // get rid of leading +
|
||||
"perSeriesAligner": query.Params.Get("aggregation.perSeriesAligner"),
|
||||
"secondaryGroupByFields": []string{},
|
||||
"unitOverride": "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeshiftDuration": "0s",
|
||||
"y1Axis": map[string]string{
|
||||
"label": "y1Axis",
|
||||
"scale": "LINEAR",
|
||||
},
|
||||
},
|
||||
"timeSelection": map[string]string{
|
||||
"timeRange": "custom",
|
||||
"start": query.Params.Get("interval.startTime"),
|
||||
"end": query.Params.Get("interval.endTime"),
|
||||
},
|
||||
}
|
||||
|
||||
blob, err := json.Marshal(pageState)
|
||||
if err != nil {
|
||||
slog.Error("Failed to generate deep link", "pageState", pageState, "ProjectName", query.ProjectName, "query", query.RefID)
|
||||
return ""
|
||||
}
|
||||
|
||||
q.Set("pageState", string(blob))
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
accountChooserURL, err := url.Parse("https://accounts.google.com/AccountChooser")
|
||||
if err != nil {
|
||||
slog.Error("Failed to generate deep link: unable to parse account chooser URL", "ProjectName", query.ProjectName, "query", query.RefID)
|
||||
return ""
|
||||
}
|
||||
accountChooserQuery := accountChooserURL.Query()
|
||||
accountChooserQuery.Set("continue", u.String())
|
||||
accountChooserURL.RawQuery = accountChooserQuery.Encode()
|
||||
|
||||
return accountChooserURL.String()
|
||||
}
|
||||
|
||||
func (e *CloudMonitoringExecutor) executeTimeSeriesQuery(ctx context.Context, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
result := &tsdb.Response{
|
||||
Results: make(map[string]*tsdb.QueryResult),
|
||||
@ -131,7 +210,17 @@ func (e *CloudMonitoringExecutor) executeTimeSeriesQuery(ctx context.Context, ts
|
||||
if err != nil {
|
||||
queryRes.Error = err
|
||||
}
|
||||
|
||||
result.Results[query.RefID] = queryRes
|
||||
|
||||
resourceType := ""
|
||||
for _, s := range resp.TimeSeries {
|
||||
resourceType = s.Resource.Type
|
||||
// set the first resource type found
|
||||
break
|
||||
}
|
||||
query.Params.Set("resourceType", resourceType)
|
||||
queryRes.Meta.Set("deepLink", query.buildDeepLink())
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ import _ from 'lodash';
|
||||
|
||||
import {
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataQueryResponseData,
|
||||
DataSourceApi,
|
||||
DataSourceInstanceSettings,
|
||||
ScopedVars,
|
||||
SelectableValue,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
@ -42,8 +43,8 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
|
||||
return this.templateSrv.getVariables().map(v => `$${v.name}`);
|
||||
}
|
||||
|
||||
async query(options: DataQueryRequest<CloudMonitoringQuery>): Promise<DataQueryResponse> {
|
||||
const result: DataQueryResponse[] = [];
|
||||
async query(options: DataQueryRequest<CloudMonitoringQuery>): Promise<DataQueryResponseData> {
|
||||
const result: DataQueryResponseData[] = [];
|
||||
const data = await this.getTimeSeries(options);
|
||||
if (data.results) {
|
||||
Object.values(data.results).forEach((queryRes: any) => {
|
||||
@ -61,7 +62,20 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
|
||||
if (unit) {
|
||||
timeSerie = { ...timeSerie, unit };
|
||||
}
|
||||
result.push(timeSerie);
|
||||
const df = toDataFrame(timeSerie);
|
||||
|
||||
for (const field of df.fields) {
|
||||
if (queryRes.meta?.deepLink && queryRes.meta?.deepLink.length > 0) {
|
||||
field.config.links = [
|
||||
{
|
||||
url: queryRes.meta?.deepLink,
|
||||
title: 'View in Metrics Explorer',
|
||||
targetBlank: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
result.push(df);
|
||||
});
|
||||
});
|
||||
return { data: result };
|
||||
|
Loading…
Reference in New Issue
Block a user