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
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user