From 71f79b9de00a40c2de646d59ad11a3d3db8e5591 Mon Sep 17 00:00:00 2001 From: juanicabanas Date: Tue, 18 Oct 2022 10:47:24 -0300 Subject: [PATCH] PublicDashboards: Fix hidden queries execution (#57004) PublicDashboards: Fix hidden queries execution --- .../queries/dashboard_queries.go | 20 +- .../queries/dashboard_queries_test.go | 95 +++++++ .../publicdashboards/service/service.go | 4 + .../publicdashboards/service/service_test.go | 267 ++++++++++++++---- 4 files changed, 325 insertions(+), 61 deletions(-) diff --git a/pkg/services/publicdashboards/queries/dashboard_queries.go b/pkg/services/publicdashboards/queries/dashboard_queries.go index 8e74b47a00f..45665d2e692 100644 --- a/pkg/services/publicdashboards/queries/dashboard_queries.go +++ b/pkg/services/publicdashboards/queries/dashboard_queries.go @@ -46,17 +46,19 @@ func GroupQueriesByPanelId(dashboard *simplejson.Json) map[int64][]*simplejson.J for _, queryObj := range panel.Get("targets").MustArray() { query := simplejson.NewFromAny(queryObj) - // We dont support exemplars for public dashboards currently - query.Del("exemplar") + if hideAttr, exists := query.CheckGet("hide"); !exists || !hideAttr.MustBool() { + // We dont support exemplars for public dashboards currently + query.Del("exemplar") - // if query target has no datasource, set it to have the datasource on the panel - if _, ok := query.CheckGet("datasource"); !ok { - uid := GetDataSourceUidFromJson(panel) - datasource := map[string]interface{}{"type": "public-ds", "uid": uid} - query.Set("datasource", datasource) + // if query target has no datasource, set it to have the datasource on the panel + if _, ok := query.CheckGet("datasource"); !ok { + uid := GetDataSourceUidFromJson(panel) + datasource := map[string]interface{}{"type": "public-ds", "uid": uid} + query.Set("datasource", datasource) + } + + panelQueries = append(panelQueries, query) } - - panelQueries = append(panelQueries, query) } result[panel.Get("id").MustInt64()] = panelQueries diff --git a/pkg/services/publicdashboards/queries/dashboard_queries_test.go b/pkg/services/publicdashboards/queries/dashboard_queries_test.go index 0ead26cffa8..a42857bbdc6 100644 --- a/pkg/services/publicdashboards/queries/dashboard_queries_test.go +++ b/pkg/services/publicdashboards/queries/dashboard_queries_test.go @@ -303,6 +303,80 @@ const ( ], "schemaVersion": 21 }` + + dashboardWithOneHiddenQuery = ` +{ + "panels": [ + { + "id": 2, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "_yxMP8Ynk" + }, + "exemplar": true, + "expr": "go_goroutines{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "A", + "hide": true + }, + { + "datasource": { + "type": "prometheus", + "uid": "promds2" + }, + "exemplar": true, + "expr": "query2", + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "Panel Title", + "type": "timeseries" + } + ], + "schemaVersion": 35 +}` + dashboardWithAllHiddenQueries = ` +{ + "panels": [ + { + "id": 2, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "_yxMP8Ynk" + }, + "exemplar": true, + "expr": "go_goroutines{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "A", + "hide": true + }, + { + "datasource": { + "type": "prometheus", + "uid": "promds2" + }, + "exemplar": true, + "expr": "query2", + "interval": "", + "legendFormat": "", + "refId": "B", + "hide": true + } + ], + "title": "Panel Title", + "type": "timeseries" + } + ], + "schemaVersion": 35 +}` ) func TestGetUniqueDashboardDatasourceUids(t *testing.T) { @@ -458,6 +532,27 @@ func TestGroupQueriesByPanelId(t *testing.T) { "refId": "A" }`, string(query)) }) + + t.Run("hidden query filtered", func(t *testing.T) { + json, err := simplejson.NewJson([]byte(dashboardWithOneHiddenQuery)) + require.NoError(t, err) + queries := GroupQueriesByPanelId(json)[2] + + require.Len(t, queries, 1) + for _, query := range queries { + if hideAttr, exists := query.CheckGet("hide"); exists && hideAttr.MustBool() { + require.Fail(t, "hidden queries should have been filtered") + } + } + }) + + t.Run("hidden query filtered, so empty queries returned", func(t *testing.T) { + json, err := simplejson.NewJson([]byte(dashboardWithAllHiddenQueries)) + require.NoError(t, err) + queries := GroupQueriesByPanelId(json)[2] + + require.Len(t, queries, 0) + }) } func TestGroupQueriesByDataSource(t *testing.T) { diff --git a/pkg/services/publicdashboards/service/service.go b/pkg/services/publicdashboards/service/service.go index 15167904110..fec02294e74 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -213,6 +213,10 @@ func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context, return nil, err } + if len(metricReq.Queries) == 0 { + return nil, nil + } + anonymousUser := pd.BuildAnonymousUser(ctx, dashboard) res, err := pd.QueryDataService.QueryData(ctx, anonymousUser, skipCache, metricReq) diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index 4c8a2a00c04..671f831df11 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -120,7 +120,7 @@ func TestSavePublicDashboard(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) - dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}) + dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) service := &PublicDashboardServiceImpl{ log: log.New("test.logger"), @@ -164,7 +164,7 @@ func TestSavePublicDashboard(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) - dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}) + dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) service := &PublicDashboardServiceImpl{ log: log.New("test.logger"), @@ -195,7 +195,7 @@ func TestSavePublicDashboard(t *testing.T) { dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) templateVars := make([]map[string]interface{}, 1) - dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, templateVars) + dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, templateVars, nil) service := &PublicDashboardServiceImpl{ log: log.New("test.logger"), @@ -255,7 +255,7 @@ func TestUpdatePublicDashboard(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) - dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}) + dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) service := &PublicDashboardServiceImpl{ log: log.New("test.logger"), @@ -316,7 +316,7 @@ func TestUpdatePublicDashboard(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) - dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}) + dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) service := &PublicDashboardServiceImpl{ log: log.New("test.logger"), @@ -365,7 +365,7 @@ func TestUpdatePublicDashboard(t *testing.T) { func TestBuildAnonymousUser(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) - dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}) + dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) publicdashboardStore := database.ProvideStore(sqlStore) service := &PublicDashboardServiceImpl{ log: log.New("test.logger"), @@ -386,7 +386,7 @@ func TestGetMetricRequest(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) - dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}) + dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) publicDashboard := &PublicDashboard{ Uid: "1", DashboardUid: dashboard.Uid, @@ -425,13 +425,67 @@ func TestGetMetricRequest(t *testing.T) { }) } +func TestGetQueryDataResponse(t *testing.T) { + sqlStore := sqlstore.InitTestDB(t) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) + publicdashboardStore := database.ProvideStore(sqlStore) + + service := &PublicDashboardServiceImpl{ + log: log.New("test.logger"), + store: publicdashboardStore, + intervalCalculator: intervalv2.NewCalculator(), + } + + publicDashboardQueryDTO := PublicDashboardQueryDTO{ + IntervalMs: int64(1), + MaxDataPoints: int64(1), + } + + t.Run("Returns nil when query is hidden", func(t *testing.T) { + hiddenQuery := map[string]interface{}{ + "datasource": map[string]interface{}{ + "type": "mysql", + "uid": "ds1", + }, + "hide": true, + "refId": "A", + } + customPanels := []interface{}{ + map[string]interface{}{ + "id": 1, + "datasource": map[string]interface{}{ + "uid": "ds1", + }, + "targets": []interface{}{hiddenQuery}, + }} + + dashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, true, []map[string]interface{}{}, customPanels) + dto := &SavePublicDashboardConfigDTO{ + DashboardUid: dashboard.Uid, + OrgId: dashboard.OrgId, + UserId: 7, + PublicDashboard: &PublicDashboard{ + IsEnabled: true, + DashboardUid: "NOTTHESAME", + OrgId: 9999999, + TimeSettings: timeSettings, + }, + } + pubdashDto, err := service.SavePublicDashboardConfig(context.Background(), SignedInUser, dto) + require.NoError(t, err) + + resp, _ := service.GetQueryDataResponse(context.Background(), true, publicDashboardQueryDTO, 1, pubdashDto.AccessToken) + require.Nil(t, resp) + }) +} + func TestBuildMetricRequest(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) - publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}) - nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true, []map[string]interface{}{}) + publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) + nonPublicDashboard := insertTestDashboard(t, dashboardStore, "testNonPublicDashie", 1, 0, true, []map[string]interface{}{}, nil) from, to := internal.GetTimeRangeFromDashboard(t, publicDashboard.Data) service := &PublicDashboardServiceImpl{ @@ -533,58 +587,167 @@ func TestBuildMetricRequest(t *testing.T) { require.ErrorContains(t, err, ErrPublicDashboardPanelNotFound.Reason) }) + + t.Run("metric request built without hidden query", func(t *testing.T) { + hiddenQuery := map[string]interface{}{ + "datasource": map[string]interface{}{ + "type": "mysql", + "uid": "ds1", + }, + "hide": true, + "refId": "A", + } + nonHiddenQuery := map[string]interface{}{ + "datasource": map[string]interface{}{ + "type": "prometheus", + "uid": "ds2", + }, + "refId": "B", + } + + customPanels := []interface{}{ + map[string]interface{}{ + "id": 1, + "datasource": map[string]interface{}{ + "uid": "ds1", + }, + "targets": []interface{}{hiddenQuery, nonHiddenQuery}, + }} + + publicDashboard := insertTestDashboard(t, dashboardStore, "testDashWithHiddenQuery", 1, 0, true, []map[string]interface{}{}, customPanels) + + reqDTO, err := service.buildMetricRequest( + context.Background(), + publicDashboard, + publicDashboardPD, + 1, + publicDashboardQueryDTO, + ) + require.NoError(t, err) + + require.Equal(t, from, reqDTO.From) + require.Equal(t, to, reqDTO.To) + + for i := range reqDTO.Queries { + require.Equal(t, publicDashboardQueryDTO.IntervalMs, reqDTO.Queries[i].Get("intervalMs").MustInt64()) + require.Equal(t, publicDashboardQueryDTO.MaxDataPoints, reqDTO.Queries[i].Get("maxDataPoints").MustInt64()) + } + + require.Len(t, reqDTO.Queries, 1) + + require.NotEqual( + t, + simplejson.NewFromAny(hiddenQuery), + reqDTO.Queries[0], + ) + + require.Equal( + t, + simplejson.NewFromAny(nonHiddenQuery), + reqDTO.Queries[0], + ) + }) + + t.Run("metric request built with 0 queries len when all queries are hidden", func(t *testing.T) { + customPanels := []interface{}{ + map[string]interface{}{ + "id": 1, + "datasource": map[string]interface{}{ + "uid": "ds1", + }, + "targets": []interface{}{map[string]interface{}{ + "datasource": map[string]interface{}{ + "type": "mysql", + "uid": "ds1", + }, + "hide": true, + "refId": "A", + }, map[string]interface{}{ + "datasource": map[string]interface{}{ + "type": "prometheus", + "uid": "ds2", + }, + "hide": true, + "refId": "B", + }}, + }} + + publicDashboard := insertTestDashboard(t, dashboardStore, "testDashWithAllQueriesHidden", 1, 0, true, []map[string]interface{}{}, customPanels) + + reqDTO, err := service.buildMetricRequest( + context.Background(), + publicDashboard, + publicDashboardPD, + 1, + publicDashboardQueryDTO, + ) + require.NoError(t, err) + + require.Equal(t, from, reqDTO.From) + require.Equal(t, to, reqDTO.To) + + require.Len(t, reqDTO.Queries, 0) + }) } func insertTestDashboard(t *testing.T, dashboardStore *dashboardsDB.DashboardStore, title string, orgId int64, - folderId int64, isFolder bool, templateVars []map[string]interface{}, tags ...interface{}) *models.Dashboard { + folderId int64, isFolder bool, templateVars []map[string]interface{}, customPanels []interface{}, tags ...interface{}) *models.Dashboard { t.Helper() + + var dashboardPanels []interface{} + if customPanels != nil { + dashboardPanels = customPanels + } else { + dashboardPanels = []interface{}{ + map[string]interface{}{ + "id": 1, + "datasource": map[string]interface{}{ + "uid": "ds1", + }, + "targets": []interface{}{ + map[string]interface{}{ + "datasource": map[string]interface{}{ + "type": "mysql", + "uid": "ds1", + }, + "refId": "A", + }, + map[string]interface{}{ + "datasource": map[string]interface{}{ + "type": "prometheus", + "uid": "ds2", + }, + "refId": "B", + }, + }, + }, + map[string]interface{}{ + "id": 2, + "datasource": map[string]interface{}{ + "uid": "ds3", + }, + "targets": []interface{}{ + map[string]interface{}{ + "datasource": map[string]interface{}{ + "type": "mysql", + "uid": "ds3", + }, + "refId": "C", + }, + }, + }, + } + } + cmd := models.SaveDashboardCommand{ OrgId: orgId, FolderId: folderId, IsFolder: isFolder, Dashboard: simplejson.NewFromAny(map[string]interface{}{ - "id": nil, - "title": title, - "tags": tags, - "panels": []interface{}{ - map[string]interface{}{ - "id": 1, - "datasource": map[string]interface{}{ - "uid": "ds1", - }, - "targets": []interface{}{ - map[string]interface{}{ - "datasource": map[string]interface{}{ - "type": "mysql", - "uid": "ds1", - }, - "refId": "A", - }, - map[string]interface{}{ - "datasource": map[string]interface{}{ - "type": "prometheus", - "uid": "ds2", - }, - "refId": "B", - }, - }, - }, - map[string]interface{}{ - "id": 2, - "datasource": map[string]interface{}{ - "uid": "ds3", - }, - "targets": []interface{}{ - map[string]interface{}{ - "datasource": map[string]interface{}{ - "type": "mysql", - "uid": "ds3", - }, - "refId": "C", - }, - }, - }, - }, + "id": nil, + "title": title, + "tags": tags, + "panels": dashboardPanels, "templating": map[string]interface{}{ "list": templateVars, },