From debbb8d59d0213deadf1b4e67ba03eb68cb95059 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Tue, 24 May 2022 09:24:55 -0400 Subject: [PATCH] sqlstore: finish removing Find and SearchDashboards (#49347) * chore: replace artisnal FakeDashboardService with generated mock Maintaining a handcrafted FakeDashboardService is not sustainable now that we are in the process of moving the dashboard-related functions out of sqlstore. * sqlstore: finish removing Find and SearchDashboards Find and SearchDashboards were previously copied into the dashboard service. This commit completes that work, removing Find and SearchDashboards from the sqlstore and updating callers to use the dashboard service. * dashboards: remove SearchDashboards from Store interface SearchDashboards is a wrapper around FindDashboard that transforms the results, so it's been moved out of the Store entirely and the functionality moved into the Dashboard Service's search implementation. The database tests depended heavily on the transformation, so I added testSearchDashboards, a copy of search dashboards, instead of (heavily) refactoring all the tests. --- pkg/services/dashboards/dashboard.go | 1 - pkg/services/dashboards/database/database.go | 60 ------- .../database/database_folder_test.go | 32 ++-- .../dashboards/database/database_test.go | 97 ++++++++--- .../dashboards/service/dashboard_service.go | 58 ++++++- pkg/services/ngalert/ngalert.go | 13 +- .../ngalert/notifier/alertmanager_test.go | 10 +- pkg/services/ngalert/store/alert_rule.go | 2 +- pkg/services/ngalert/store/database.go | 11 +- pkg/services/ngalert/tests/util.go | 7 +- pkg/services/search/service.go | 19 ++- pkg/services/search/service_test.go | 30 ++-- pkg/services/sqlstore/dashboard.go | 157 ------------------ .../sqlstore/searchstore/search_test.go | 18 +- pkg/services/sqlstore/store.go | 1 - 15 files changed, 212 insertions(+), 304 deletions(-) diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index b50af60fd64..3797a355237 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -62,7 +62,6 @@ type Store interface { SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) SavePublicDashboardConfig(cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboardConfig, error) - SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error UnprovisionDashboard(ctx context.Context, id int64) error UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error // ValidateDashboardBeforeSave validates a dashboard before save. diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 684477e7291..533afb7d2a7 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -930,66 +930,6 @@ func (d *DashboardStore) GetDashboards(ctx context.Context, query *models.GetDas }) } -func (d *DashboardStore) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error { - res, err := d.FindDashboards(ctx, query) - if err != nil { - return err - } - - makeQueryResult(query, res) - - return nil -} - -func getHitType(item dashboards.DashboardSearchProjection) models.HitType { - var hitType models.HitType - if item.IsFolder { - hitType = models.DashHitFolder - } else { - hitType = models.DashHitDB - } - - return hitType -} - -func makeQueryResult(query *models.FindPersistedDashboardsQuery, res []dashboards.DashboardSearchProjection) { - query.Result = make([]*models.Hit, 0) - hits := make(map[int64]*models.Hit) - - for _, item := range res { - hit, exists := hits[item.ID] - if !exists { - hit = &models.Hit{ - ID: item.ID, - UID: item.UID, - Title: item.Title, - URI: "db/" + item.Slug, - URL: models.GetDashboardFolderUrl(item.IsFolder, item.UID, item.Slug), - Type: getHitType(item), - FolderID: item.FolderID, - FolderUID: item.FolderUID, - FolderTitle: item.FolderTitle, - Tags: []string{}, - } - - if item.FolderID > 0 { - hit.FolderURL = models.GetFolderUrl(item.FolderUID, item.FolderSlug) - } - - if query.Sort.MetaName != "" { - hit.SortMeta = item.SortMeta - hit.SortMetaName = query.Sort.MetaName - } - - query.Result = append(query.Result, hit) - hits[item.ID] = hit - } - if len(item.Term) > 0 { - hit.Tags = append(hit.Tags, item.Term) - } - } -} - func (d *DashboardStore) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) { filters := []interface{}{ permissions.DashboardPermissionFilter{ diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index ae38bb2b85e..dc1e07bf34c 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -42,7 +42,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) require.Equal(t, query.Result[0].ID, folder.Id) @@ -65,7 +65,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 1) @@ -84,7 +84,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) require.Equal(t, query.Result[0].ID, folder.Id) @@ -103,7 +103,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) require.Equal(t, query.Result[0].ID, folder.Id) @@ -125,7 +125,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { query := &models.FindPersistedDashboardsQuery{ SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 1) require.Equal(t, query.Result[0].ID, dashInRoot.Id) @@ -139,7 +139,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("should be able to search for child dashboard but not folder", func(t *testing.T) { query := &models.FindPersistedDashboardsQuery{SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}} - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) require.Equal(t, query.Result[0].ID, childDash.Id) @@ -158,7 +158,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id, childDash.Id}, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 3) require.Equal(t, query.Result[0].ID, folder.Id) @@ -197,7 +197,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }, OrgId: 1, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 4) require.Equal(t, query.Result[0].ID, folder1.Id) @@ -223,7 +223,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgId: 1, DashboardIds: []int64{folder1.Id, childDash1.Id, childDash2.Id, dashInRoot.Id}, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 1) require.Equal(t, query.Result[0].ID, dashInRoot.Id) @@ -239,7 +239,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgId: 1, DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id}, } - err := sqlStore.SearchDashboards(context.Background(), query) + err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 4) require.Equal(t, query.Result[0].ID, folder2.Id) @@ -263,7 +263,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgId: 1, DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id}, } - err = sqlStore.SearchDashboards(context.Background(), query) + err = testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 4) require.Equal(t, query.Result[0].ID, folder2.Id) @@ -302,7 +302,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { Type: "dash-folder", } - err := sqlStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) @@ -355,7 +355,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { } t.Run("Should have write access to all dashboard folders with default ACL", func(t *testing.T) { - err := sqlStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) @@ -387,7 +387,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) require.NoError(t, err) - err = sqlStore.SearchDashboards(context.Background(), &query) + err = testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 1) @@ -421,7 +421,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { } t.Run("Should have no write access to any dashboard folders with default ACL", func(t *testing.T) { - err := sqlStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 0) @@ -453,7 +453,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) require.NoError(t, err) - err = sqlStore.SearchDashboards(context.Background(), &query) + err = testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 1) diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index c568f646b7c..7ad04c3e053 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -242,10 +242,9 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { SignedInUser: &models.SignedInUser{}, } - err = dashboardStore.SearchDashboards(context.Background(), &query) + res, err := dashboardStore.FindDashboards(context.Background(), &query) require.NoError(t, err) - - require.Equal(t, len(query.Result), 0) + require.Equal(t, len(res), 0) sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { var existingRuleID int64 @@ -301,7 +300,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { require.Equal(t, len(query.Result), 2) }) - t.Run("Should be able to search for dashboard folder", func(t *testing.T) { + t.Run("Should be able to find dashboard folder", func(t *testing.T) { setup() query := models.FindPersistedDashboardsQuery{ Title: "1 test dash folder", @@ -315,7 +314,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { }, } - err := dashboardStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 1) @@ -325,7 +324,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { require.Equal(t, hit.FolderTitle, "") }) - t.Run("Should be able to limit search", func(t *testing.T) { + t.Run("Should be able to limit find results", func(t *testing.T) { setup() query := models.FindPersistedDashboardsQuery{ OrgId: 1, @@ -339,14 +338,14 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { }, } - err := dashboardStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 1) require.EqualValues(t, query.Result[0].Title, "1 test dash folder") }) - t.Run("Should be able to search beyond limit using paging", func(t *testing.T) { + t.Run("Should be able to find results beyond limit using paging", func(t *testing.T) { setup() query := models.FindPersistedDashboardsQuery{ OrgId: 1, @@ -364,7 +363,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { }, } - err := dashboardStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 1) @@ -386,14 +385,14 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { }, } - err := dashboardStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 3) require.Equal(t, query.Result[0].Title, "test dash 23") }) - t.Run("Should be able to search for a dashboard folder's children", func(t *testing.T) { + t.Run("Should be able to find a dashboard folder's children", func(t *testing.T) { setup() query := models.FindPersistedDashboardsQuery{ OrgId: 1, @@ -407,7 +406,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { }, } - err := dashboardStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) @@ -420,7 +419,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { require.Equal(t, hit.FolderURL, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug)) }) - t.Run("Should be able to search for dashboard by dashboard ids", func(t *testing.T) { + t.Run("Should be able to find dashboards by ids", func(t *testing.T) { setup() query := models.FindPersistedDashboardsQuery{ DashboardIds: []int64{savedDash.Id, savedDash2.Id}, @@ -433,7 +432,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { }, } - err := dashboardStore.SearchDashboards(context.Background(), &query) + err := testSearchDashboards(dashboardStore, &query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) @@ -445,7 +444,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { require.Equal(t, len(hit2.Tags), 1) }) - t.Run("Should be able to search for starred dashboards", func(t *testing.T) { + t.Run("Should be able to find starred dashboards", func(t *testing.T) { setup() starredDash := insertTestDashboard(t, dashboardStore, "starred dash", 1, 0, false) err := starService.Add(context.Background(), &star.StarDashboardCommand{ @@ -471,11 +470,11 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { }, IsStarred: true, } - err = dashboardStore.SearchDashboards(context.Background(), &query) + res, err := dashboardStore.FindDashboards(context.Background(), &query) require.NoError(t, err) - require.Equal(t, len(query.Result), 1) - require.Equal(t, query.Result[0].Title, "starred dash") + require.Equal(t, len(res), 1) + require.Equal(t, res[0].Title, "starred dash") }) } @@ -516,7 +515,7 @@ func TestIntegrationDashboard_SortingOptions(t *testing.T) { }, }, } - results, err := sqlStore.FindDashboards(context.Background(), qNoSort) + results, err := dashboardStore.FindDashboards(context.Background(), qNoSort) require.NoError(t, err) require.Len(t, results, 2) assert.Equal(t, dashA.Id, results[0].ID) @@ -537,7 +536,7 @@ func TestIntegrationDashboard_SortingOptions(t *testing.T) { }, }, } - results, err = sqlStore.FindDashboards(context.Background(), qSort) + results, err = dashboardStore.FindDashboards(context.Background(), qSort) require.NoError(t, err) require.Len(t, results, 2) assert.Equal(t, dashB.Id, results[0].ID) @@ -560,7 +559,7 @@ func TestIntegrationDashboard_Filter(t *testing.T) { }, }, } - results, err := sqlStore.FindDashboards(context.Background(), qNoFilter) + results, err := dashboardStore.FindDashboards(context.Background(), qNoFilter) require.NoError(t, err) require.Len(t, results, 2) @@ -580,7 +579,7 @@ func TestIntegrationDashboard_Filter(t *testing.T) { }, }, } - results, err = sqlStore.FindDashboards(context.Background(), qFilter) + results, err = dashboardStore.FindDashboards(context.Background(), qFilter) require.NoError(t, err) require.Len(t, results, 1) assert.Equal(t, dashB.Id, results[0].ID) @@ -729,3 +728,57 @@ func updateDashboardAcl(t *testing.T, dashboardStore *DashboardStore, dashboardI return dashboardStore.UpdateDashboardACL(context.Background(), dashboardID, itemPtrs) } + +// testSearchDashboards is a (near) copy of the dashboard service +// SearchDashboards, which is a wrapper around FindDashboards. +func testSearchDashboards(d *DashboardStore, query *models.FindPersistedDashboardsQuery) error { + res, err := d.FindDashboards(context.Background(), query) + if err != nil { + return err + } + makeQueryResult(query, res) + return nil +} + +func makeQueryResult(query *models.FindPersistedDashboardsQuery, res []dashboards.DashboardSearchProjection) { + query.Result = make([]*models.Hit, 0) + hits := make(map[int64]*models.Hit) + + for _, item := range res { + hit, exists := hits[item.ID] + if !exists { + hitType := models.DashHitDB + if item.IsFolder { + hitType = models.DashHitFolder + } + + hit = &models.Hit{ + ID: item.ID, + UID: item.UID, + Title: item.Title, + URI: "db/" + item.Slug, + URL: models.GetDashboardFolderUrl(item.IsFolder, item.UID, item.Slug), + Type: hitType, + FolderID: item.FolderID, + FolderUID: item.FolderUID, + FolderTitle: item.FolderTitle, + Tags: []string{}, + } + + if item.FolderID > 0 { + hit.FolderURL = models.GetFolderUrl(item.FolderUID, item.FolderSlug) + } + + if query.Sort.MetaName != "" { + hit.SortMeta = item.SortMeta + hit.SortMetaName = query.Sort.MetaName + } + + query.Result = append(query.Result, hit) + hits[item.ID] = hit + } + if len(item.Term) > 0 { + hit.Tags = append(hit.Tags, item.Term) + } + } +} diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 2be7f6fc686..a035374fde5 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -528,5 +528,61 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *model } func (dr *DashboardServiceImpl) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error { - return dr.dashboardStore.SearchDashboards(ctx, query) + res, err := dr.FindDashboards(ctx, query) + if err != nil { + return err + } + + makeQueryResult(query, res) + + return nil +} + +func getHitType(item dashboards.DashboardSearchProjection) models.HitType { + var hitType models.HitType + if item.IsFolder { + hitType = models.DashHitFolder + } else { + hitType = models.DashHitDB + } + + return hitType +} + +func makeQueryResult(query *models.FindPersistedDashboardsQuery, res []dashboards.DashboardSearchProjection) { + query.Result = make([]*models.Hit, 0) + hits := make(map[int64]*models.Hit) + + for _, item := range res { + hit, exists := hits[item.ID] + if !exists { + hit = &models.Hit{ + ID: item.ID, + UID: item.UID, + Title: item.Title, + URI: "db/" + item.Slug, + URL: models.GetDashboardFolderUrl(item.IsFolder, item.UID, item.Slug), + Type: getHitType(item), + FolderID: item.FolderID, + FolderUID: item.FolderUID, + FolderTitle: item.FolderTitle, + Tags: []string{}, + } + + if item.FolderID > 0 { + hit.FolderURL = models.GetFolderUrl(item.FolderUID, item.FolderSlug) + } + + if query.Sort.MetaName != "" { + hit.SortMeta = item.SortMeta + hit.SortMetaName = query.Sort.MetaName + } + + query.Result = append(query.Result, hit) + hits[item.ID] = hit + } + if len(item.Term) > 0 { + hit.Tags = append(hit.Tags, item.Term) + } + } } diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 6c152ea138b..6ae853e1326 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -96,12 +96,13 @@ func (ng *AlertNG) init() error { var err error store := &store.DBstore{ - BaseInterval: ng.Cfg.UnifiedAlerting.BaseInterval, - DefaultInterval: ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval, - SQLStore: ng.SQLStore, - Logger: ng.Log, - FolderService: ng.folderService, - AccessControl: ng.accesscontrol, + BaseInterval: ng.Cfg.UnifiedAlerting.BaseInterval, + DefaultInterval: ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval, + SQLStore: ng.SQLStore, + Logger: ng.Log, + FolderService: ng.folderService, + AccessControl: ng.accesscontrol, + DashboardService: ng.dashboardService, } decryptFn := ng.SecretsService.GetDecryptedValue diff --git a/pkg/services/ngalert/notifier/alertmanager_test.go b/pkg/services/ngalert/notifier/alertmanager_test.go index 5e2f10e7420..c308e800dca 100644 --- a/pkg/services/ngalert/notifier/alertmanager_test.go +++ b/pkg/services/ngalert/notifier/alertmanager_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/secrets/database" "github.com/go-openapi/strfmt" @@ -35,10 +36,11 @@ func setupAMTest(t *testing.T) *Alertmanager { m := metrics.NewAlertmanagerMetrics(prometheus.NewRegistry()) sqlStore := sqlstore.InitTestDB(t) s := &store.DBstore{ - BaseInterval: 10 * time.Second, - DefaultInterval: 60 * time.Second, - SQLStore: sqlStore, - Logger: log.New("alertmanager-test"), + BaseInterval: 10 * time.Second, + DefaultInterval: 60 * time.Second, + SQLStore: sqlStore, + Logger: log.New("alertmanager-test"), + DashboardService: dashboards.NewFakeDashboardService(t), } kvStore := NewFakeKVStore(t) diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 0402942bc8e..f3c0319e76a 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -299,7 +299,7 @@ func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, use for { query := searchQuery query.Page = page - proj, err := st.SQLStore.FindDashboards(ctx, &query) + proj, err := st.DashboardService.FindDashboards(ctx, &query) if err != nil { return nil, err } diff --git a/pkg/services/ngalert/store/database.go b/pkg/services/ngalert/store/database.go index 52ea19e7029..7e0d40ae94b 100644 --- a/pkg/services/ngalert/store/database.go +++ b/pkg/services/ngalert/store/database.go @@ -31,9 +31,10 @@ type DBstore struct { // the base scheduler tick rate; it's used for validating definition interval BaseInterval time.Duration // default alert definiiton interval - DefaultInterval time.Duration - SQLStore *sqlstore.SQLStore - Logger log.Logger - FolderService dashboards.FolderService - AccessControl accesscontrol.AccessControl + DefaultInterval time.Duration + SQLStore *sqlstore.SQLStore + Logger log.Logger + FolderService dashboards.FolderService + AccessControl accesscontrol.AccessControl + DashboardService dashboards.DashboardService } diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index 20954caf973..373de7d4801 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -70,9 +70,10 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, * ) require.NoError(t, err) return ng, &store.DBstore{ - SQLStore: ng.SQLStore, - BaseInterval: baseInterval * time.Second, - Logger: log.New("ngalert-test"), + SQLStore: ng.SQLStore, + BaseInterval: baseInterval * time.Second, + Logger: log.New("ngalert-test"), + DashboardService: dashboardService, } } diff --git a/pkg/services/search/service.go b/pkg/services/search/service.go index fc5c1a0bf06..6cfddb57eb7 100644 --- a/pkg/services/search/service.go +++ b/pkg/services/search/service.go @@ -4,6 +4,7 @@ import ( "context" "sort" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/star" "github.com/grafana/grafana/pkg/setting" @@ -11,15 +12,16 @@ import ( "github.com/grafana/grafana/pkg/models" ) -func ProvideService(cfg *setting.Cfg, sqlstore *sqlstore.SQLStore, starService star.Service) *SearchService { +func ProvideService(cfg *setting.Cfg, sqlstore *sqlstore.SQLStore, starService star.Service, dashboardService dashboards.DashboardService) *SearchService { s := &SearchService{ Cfg: cfg, sortOptions: map[string]models.SortOption{ SortAlphaAsc.Name: SortAlphaAsc, SortAlphaDesc.Name: SortAlphaDesc, }, - sqlstore: sqlstore, - starService: starService, + sqlstore: sqlstore, + starService: starService, + dashboardService: dashboardService, } return s } @@ -47,10 +49,11 @@ type Service interface { } type SearchService struct { - Cfg *setting.Cfg - sortOptions map[string]models.SortOption - sqlstore sqlstore.Store - starService star.Service + Cfg *setting.Cfg + sortOptions map[string]models.SortOption + sqlstore sqlstore.Store + starService star.Service + dashboardService dashboards.DashboardService } func (s *SearchService) SearchHandler(ctx context.Context, query *Query) error { @@ -71,7 +74,7 @@ func (s *SearchService) SearchHandler(ctx context.Context, query *Query) error { dashboardQuery.Sort = sortOpt } - if err := s.sqlstore.SearchDashboards(ctx, &dashboardQuery); err != nil { + if err := s.dashboardService.SearchDashboards(ctx, &dashboardQuery); err != nil { return err } diff --git a/pkg/services/search/service_test.go b/pkg/services/search/service_test.go index b3ac23e60a0..9d9ba74d77e 100644 --- a/pkg/services/search/service_test.go +++ b/pkg/services/search/service_test.go @@ -4,29 +4,37 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/star" "github.com/grafana/grafana/pkg/services/star/startest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestSearch_SortedResults(t *testing.T) { ss := startest.NewStarServiceFake() ms := mockstore.NewSQLStoreMock() - ms.ExpectedPersistedDashboards = models.HitList{ - &models.Hit{ID: 16, Title: "CCAA", Type: "dash-db", Tags: []string{"BB", "AA"}}, - &models.Hit{ID: 10, Title: "AABB", Type: "dash-db", Tags: []string{"CC", "AA"}}, - &models.Hit{ID: 15, Title: "BBAA", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}}, - &models.Hit{ID: 25, Title: "bbAAa", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}}, - &models.Hit{ID: 17, Title: "FOLDER", Type: "dash-folder"}, - } + ds := dashboards.NewFakeDashboardService(t) + ds.On("SearchDashboards", mock.Anything, mock.AnythingOfType("*models.FindPersistedDashboardsQuery")).Run(func(args mock.Arguments) { + q := args.Get(1).(*models.FindPersistedDashboardsQuery) + q.Result = models.HitList{ + &models.Hit{ID: 16, Title: "CCAA", Type: "dash-db", Tags: []string{"BB", "AA"}}, + &models.Hit{ID: 10, Title: "AABB", Type: "dash-db", Tags: []string{"CC", "AA"}}, + &models.Hit{ID: 15, Title: "BBAA", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}}, + &models.Hit{ID: 25, Title: "bbAAa", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}}, + &models.Hit{ID: 17, Title: "FOLDER", Type: "dash-folder"}, + } + }).Return(nil) ms.ExpectedSignedInUser = &models.SignedInUser{IsGrafanaAdmin: true} ss.ExpectedUserStars = &star.GetUserStarsResult{UserStars: map[int64]bool{10: true, 12: true}} svc := &SearchService{ - sqlstore: ms, - starService: ss, + sqlstore: ms, + starService: ss, + dashboardService: ds, } query := &Query{ diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index 7f77158e748..5d500baa12a 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -7,9 +7,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/sqlstore/permissions" - "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/util" ) @@ -27,160 +24,6 @@ func init() { var generateNewUid func() string = util.GenerateShortUID -type DashboardSearchProjection struct { - ID int64 `xorm:"id"` - UID string `xorm:"uid"` - Title string - Slug string - Term string - IsFolder bool - FolderID int64 `xorm:"folder_id"` - FolderUID string `xorm:"folder_uid"` - FolderSlug string - FolderTitle string - SortMeta int64 -} - -func (ss *SQLStore) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { - filters := []interface{}{ - permissions.DashboardPermissionFilter{ - OrgRole: query.SignedInUser.OrgRole, - OrgId: query.SignedInUser.OrgId, - Dialect: dialect, - UserId: query.SignedInUser.UserId, - PermissionLevel: query.Permission, - }, - } - - if !accesscontrol.IsDisabled(ss.Cfg) { - // if access control is enabled, overwrite the filters so far - filters = []interface{}{ - permissions.NewAccessControlDashboardPermissionFilter(query.SignedInUser, query.Permission, query.Type), - } - } - - for _, filter := range query.Sort.Filter { - filters = append(filters, filter) - } - - filters = append(filters, query.Filters...) - - if query.OrgId != 0 { - filters = append(filters, searchstore.OrgFilter{OrgId: query.OrgId}) - } else if query.SignedInUser.OrgId != 0 { - filters = append(filters, searchstore.OrgFilter{OrgId: query.SignedInUser.OrgId}) - } - - if len(query.Tags) > 0 { - filters = append(filters, searchstore.TagsFilter{Tags: query.Tags}) - } - - if len(query.DashboardIds) > 0 { - filters = append(filters, searchstore.DashboardFilter{IDs: query.DashboardIds}) - } - - if query.IsStarred { - filters = append(filters, searchstore.StarredFilter{UserId: query.SignedInUser.UserId}) - } - - if len(query.Title) > 0 { - filters = append(filters, searchstore.TitleFilter{Dialect: dialect, Title: query.Title}) - } - - if len(query.Type) > 0 { - filters = append(filters, searchstore.TypeFilter{Dialect: dialect, Type: query.Type}) - } - - if len(query.FolderIds) > 0 { - filters = append(filters, searchstore.FolderFilter{IDs: query.FolderIds}) - } - - var res []DashboardSearchProjection - sb := &searchstore.Builder{Dialect: dialect, Filters: filters} - - limit := query.Limit - if limit < 1 { - limit = 1000 - } - - page := query.Page - if page < 1 { - page = 1 - } - - sql, params := sb.ToSQL(limit, page) - - err := ss.WithDbSession(ctx, func(dbSession *DBSession) error { - return dbSession.SQL(sql, params...).Find(&res) - }) - - if err != nil { - return nil, err - } - - return res, nil -} - -func (ss *SQLStore) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error { - res, err := ss.FindDashboards(ctx, query) - if err != nil { - return err - } - - makeQueryResult(query, res) - - return nil -} - -func getHitType(item DashboardSearchProjection) models.HitType { - var hitType models.HitType - if item.IsFolder { - hitType = models.DashHitFolder - } else { - hitType = models.DashHitDB - } - - return hitType -} - -func makeQueryResult(query *models.FindPersistedDashboardsQuery, res []DashboardSearchProjection) { - query.Result = make([]*models.Hit, 0) - hits := make(map[int64]*models.Hit) - - for _, item := range res { - hit, exists := hits[item.ID] - if !exists { - hit = &models.Hit{ - ID: item.ID, - UID: item.UID, - Title: item.Title, - URI: "db/" + item.Slug, - URL: models.GetDashboardFolderUrl(item.IsFolder, item.UID, item.Slug), - Type: getHitType(item), - FolderID: item.FolderID, - FolderUID: item.FolderUID, - FolderTitle: item.FolderTitle, - Tags: []string{}, - } - - if item.FolderID > 0 { - hit.FolderURL = models.GetFolderUrl(item.FolderUID, item.FolderSlug) - } - - if query.Sort.MetaName != "" { - hit.SortMeta = item.SortMeta - hit.SortMetaName = query.Sort.MetaName - } - - query.Result = append(query.Result, hit) - hits[item.ID] = hit - } - if len(item.Term) > 0 { - hit.Tags = append(hit.Tags, item.Term) - } - } -} - func (ss *SQLStore) GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error { sql := `SELECT diff --git a/pkg/services/sqlstore/searchstore/search_test.go b/pkg/services/sqlstore/searchstore/search_test.go index 1d17b593d50..4b4dd130cb5 100644 --- a/pkg/services/sqlstore/searchstore/search_test.go +++ b/pkg/services/sqlstore/searchstore/search_test.go @@ -5,14 +5,16 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/util" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) const ( @@ -43,7 +45,7 @@ func TestBuilder_EqualResults_Basic(t *testing.T) { Dialect: db.Dialect, } - res := []sqlstore.DashboardSearchProjection{} + res := []dashboards.DashboardSearchProjection{} err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { sql, params := builder.ToSQL(limit, page) return sess.SQL(sql, params...).Find(&res) @@ -52,7 +54,7 @@ func TestBuilder_EqualResults_Basic(t *testing.T) { assert.Len(t, res, 1) res[0].UID = "" - assert.EqualValues(t, []sqlstore.DashboardSearchProjection{ + assert.EqualValues(t, []dashboards.DashboardSearchProjection{ { ID: dashIds[0], Title: "A", @@ -80,9 +82,9 @@ func TestBuilder_Pagination(t *testing.T) { Dialect: db.Dialect, } - resPg1 := []sqlstore.DashboardSearchProjection{} - resPg2 := []sqlstore.DashboardSearchProjection{} - resPg3 := []sqlstore.DashboardSearchProjection{} + resPg1 := []dashboards.DashboardSearchProjection{} + resPg2 := []dashboards.DashboardSearchProjection{} + resPg3 := []dashboards.DashboardSearchProjection{} err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { sql, params := builder.ToSQL(15, 1) err := sess.SQL(sql, params...).Find(&resPg1) @@ -135,7 +137,7 @@ func TestBuilder_Permissions(t *testing.T) { Dialect: db.Dialect, } - res := []sqlstore.DashboardSearchProjection{} + res := []dashboards.DashboardSearchProjection{} err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { sql, params := builder.ToSQL(limit, page) return sess.SQL(sql, params...).Find(&res) diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index 03b0e31f465..531da0f55b0 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -97,7 +97,6 @@ type Store interface { SearchOrgUsers(ctx context.Context, query *models.SearchOrgUsersQuery) error RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgUserCommand) error GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error - SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error GetDataSources(ctx context.Context, query *models.GetDataSourcesQuery) error GetDataSourcesByType(ctx context.Context, query *models.GetDataSourcesByTypeQuery) error