diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go index c7f1910727a..4b62817693b 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go @@ -399,18 +399,15 @@ func Test_OnlyQueriesStatusFromGMSWhenRequired(t *testing.T) { func Test_DeletedDashboardsNotMigrated(t *testing.T) { s := setUpServiceTest(t, false).(*Service) - /** NOTE: this is not used at the moment since we changed the service - // modify what the mock returns for just this test case dashMock := s.dashboardService.(*dashboards.FakeDashboardService) - dashMock.On("GetAllDashboards", mock.Anything).Return( + dashMock.On("GetAllDashboardsByOrgId", mock.Anything).Return( []*dashboards.Dashboard{ {UID: "1", OrgID: 1, Data: simplejson.New()}, {UID: "2", OrgID: 1, Data: simplejson.New(), Deleted: time.Now()}, }, nil, ) - */ data, err := s.getMigrationDataJSON(context.TODO(), &user.SignedInUser{OrgID: 1}) assert.NoError(t, err) diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go index 4c2a454de03..1b5cb94a971 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go @@ -277,7 +277,7 @@ func (s *Service) getDashboardAndFolderCommands(ctx context.Context, signedInUse ctx, span := s.tracer.Start(ctx, "CloudMigrationService.getDashboardAndFolderCommands") defer span.End() - dashs, err := s.store.GetAllDashboardsByOrgId(ctx, signedInUser.GetOrgID()) + dashs, err := s.dashboardService.GetAllDashboardsByOrgId(ctx, signedInUser.GetOrgID()) if err != nil { return nil, nil, err } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/store.go b/pkg/services/cloudmigration/cloudmigrationimpl/store.go index 2a36c1acfdc..f8f7dd76263 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/store.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/store.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana/pkg/services/cloudmigration" - "github.com/grafana/grafana/pkg/services/dashboards" ) type store interface { @@ -26,7 +25,4 @@ type store interface { // - GetSnapshotResources(ctx context.Context, snapshotUid string, page int, limit int) ([]cloudmigration.CloudMigrationResource, error) // - GetSnapshotResourceStats(ctx context.Context, snapshotUid string) (*cloudmigration.SnapshotResourceStats, error) // - DeleteSnapshotResources(ctx context.Context, snapshotUid string) error - - // TODO move this function dashboards/databases/databases.go - GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go index a540beb10c0..a3b088b09a5 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/cloudmigration" - "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/secrets" secretskv "github.com/grafana/grafana/pkg/services/secrets/kvstore" "github.com/grafana/grafana/pkg/services/sqlstore" @@ -465,19 +464,3 @@ func (ss *sqlStore) decryptToken(ctx context.Context, cm *cloudmigration.CloudMi return nil } - -// TODO move this function dashboards/databases/databases.go -func (ss *sqlStore) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) { - //ctx, span := tracer.Start(ctx, "dashboards.database.GetAllDashboardsByOrgId") - //defer span.End() - - var dashs = make([]*dashboards.Dashboard, 0) - err := ss.db.WithDbSession(ctx, func(session *db.Session) error { - // "deleted IS NULL" is to avoid deleted dashboards - return session.Where("org_id = ? AND deleted IS NULL", orgID).Find(&dashs) - }) - if err != nil { - return nil, err - } - return dashs, nil -} diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index abdc067eddb..6931b69ffb7 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -31,6 +31,7 @@ type DashboardService interface { CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, user identity.Requester) (int64, error) GetDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*Dashboard, error) GetAllDashboards(ctx context.Context) ([]*Dashboard, error) + GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error) SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUid string) error RestoreDashboard(ctx context.Context, dashboard *Dashboard, user identity.Requester, optionalFolderUID string) error CleanUpDeletedDashboards(ctx context.Context) (int64, error) @@ -86,6 +87,7 @@ type Store interface { DeleteDashboardsInFolders(ctx context.Context, request *DeleteDashboardsInFolderRequest) error GetAllDashboards(ctx context.Context) ([]*Dashboard, error) + GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error) GetSoftDeletedExpiredDashboards(ctx context.Context, duration time.Duration) ([]*Dashboard, error) SoftDeleteDashboard(ctx context.Context, orgID int64, dashboardUid string) error SoftDeleteDashboardsInFolders(ctx context.Context, orgID int64, folderUids []string) error diff --git a/pkg/services/dashboards/dashboard_service_mock.go b/pkg/services/dashboards/dashboard_service_mock.go index 120d94c4ff8..14d43ac5e7c 100644 --- a/pkg/services/dashboards/dashboard_service_mock.go +++ b/pkg/services/dashboards/dashboard_service_mock.go @@ -198,6 +198,36 @@ func (_m *FakeDashboardService) GetAllDashboards(ctx context.Context) ([]*Dashbo return r0, r1 } + +func (_m *FakeDashboardService) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetAllDashboardsByOrgId") + } + + var r0 []*Dashboard + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*Dashboard, error)); ok { + return rf(ctx, orgID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) []*Dashboard); ok { + r0 = rf(ctx, orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*Dashboard) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, orgID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetDashboard provides a mock function with given fields: ctx, query func (_m *FakeDashboardService) GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error) { ret := _m.Called(ctx, query) diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 320a23a4dfd..537c6137e44 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -1048,6 +1048,21 @@ func (d *dashboardStore) GetAllDashboards(ctx context.Context) ([]*dashboards.Da return dashboards, nil } +func (d *dashboardStore) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) { + ctx, span := tracer.Start(ctx, "dashboards.database.GetAllDashboardsByOrgId") + defer span.End() + + var dashs = make([]*dashboards.Dashboard, 0) + err := d.store.WithDbSession(ctx, func(session *db.Session) error { + // "deleted IS NULL" is to avoid deleted dashboards + return session.Where("org_id = ? AND deleted IS NULL", orgID).Find(&dashs) + }) + if err != nil { + return nil, err + } + return dashs, nil +} + func (d *dashboardStore) GetSoftDeletedExpiredDashboards(ctx context.Context, duration time.Duration) ([]*dashboards.Dashboard, error) { ctx, span := tracer.Start(ctx, "dashboards.database.GetSoftDeletedExpiredDashboards") defer span.End() diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 7efa8be2bc5..e43ae74d971 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -266,6 +266,32 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { assert.Equal(t, len(queryResult), 1) }) + t.Run("Should be able to get all dashboards for an org", func(t *testing.T) { + setup() + dash1 := insertTestDashboard(t, dashboardStore, "org3test1", 3, 0, "", false, "org 1 test 1") + dash2 := insertTestDashboard(t, dashboardStore, "org3test2", 3, 0, "", false, "org 1 test 2") + dash3 := insertTestDashboard(t, dashboardStore, "org4test1", 4, 0, "", false, "org 2 test 1") + + dashs, err := dashboardStore.GetAllDashboardsByOrgId(context.Background(), 3) + require.NoError(t, err) + require.Equal(t, len(dashs), 2) + uids := []string{} + for _, d := range dashs { + uids = append(uids, d.UID) + } + require.Contains(t, uids, dash1.UID) + require.Contains(t, uids, dash2.UID) + + dashs, err = dashboardStore.GetAllDashboardsByOrgId(context.Background(), 4) + require.NoError(t, err) + require.Equal(t, len(dashs), 1) + require.Equal(t, dash3.UID, dashs[0].UID) + + dashs, err = dashboardStore.GetAllDashboardsByOrgId(context.Background(), 5) + require.NoError(t, err) + require.Equal(t, len(dashs), 0) + }) + t.Run("Should be able to create dashboard", func(t *testing.T) { setup() cmd := dashboards.SaveDashboardCommand{ diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 57f708911a5..0b33fc993a1 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -903,6 +903,14 @@ func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashbo return dr.dashboardStore.GetAllDashboards(ctx) } +func (dr *DashboardServiceImpl) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + return dr.listDashboardsThroughK8s(ctx, orgID) + } + + return dr.dashboardStore.GetAllDashboardsByOrgId(ctx, orgID) +} + func getHitType(item dashboards.DashboardSearchProjection) model.HitType { var hitType model.HitType if item.IsFolder { diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 8baebad32cb..45f800b950f 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -515,6 +515,58 @@ func TestGetAllDashboards(t *testing.T) { }) } +func TestGetAllDashboardsByOrgId(t *testing.T) { + fakeStore := dashboards.FakeDashboardStore{} + defer fakeStore.AssertExpectations(t) + service := &DashboardServiceImpl{ + cfg: setting.NewCfg(), + dashboardStore: &fakeStore, + } + + t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { + service.features = featuremgmt.WithFeatures() + fakeStore.On("GetAllDashboardsByOrgId", mock.Anything).Return([]*dashboards.Dashboard{}, nil).Once() + dashboard, err := service.GetAllDashboardsByOrgId(context.Background(), 1) + require.NoError(t, err) + require.NotNil(t, dashboard) + fakeStore.AssertExpectations(t) + }) + + t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { + ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service) + + dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{ + "metadata": map[string]any{ + "name": "uid", + }, + "spec": map[string]any{ + "test": "test", + "version": int64(1), + "title": "testing slugify", + }, + }} + + dashboardExpected := dashboards.Dashboard{ + UID: "uid", // uid is the name of the k8s object + Title: "testing slugify", + Slug: "testing-slugify", // slug is taken from title + OrgID: 1, // orgID is populated from the query + Version: 1, // default to version 1 + Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid", "version": int64(1)}), + } + + k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once() + k8sResourceMock.On("List", mock.Anything, mock.Anything).Return(&unstructured.UnstructuredList{Items: []unstructured.Unstructured{dashboardUnstructured}}, nil).Once() + + dashes, err := service.GetAllDashboardsByOrgId(ctx, 1) + require.NoError(t, err) + require.NotNil(t, dashes) + k8sClientMock.AssertExpectations(t) + // make sure the conversion is working + require.True(t, reflect.DeepEqual(dashes, []*dashboards.Dashboard{&dashboardExpected})) + }) +} + func TestSaveDashboard(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index 93121e0eff6..29e8399a779 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -208,6 +208,38 @@ func (_m *FakeDashboardStore) GetAllDashboards(ctx context.Context) ([]*Dashboar return r0, r1 } + +// GetAllDashboardsByOrgId provides a mock function with given fields: ctx +func (_m *FakeDashboardStore) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetAllDashboardsByOrgId") + } + + var r0 []*Dashboard + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*Dashboard, error)); ok { + return rf(ctx, orgID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) []*Dashboard); ok { + r0 = rf(ctx, orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*Dashboard) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, orgID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + + // GetDashboard provides a mock function with given fields: ctx, query func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error) { ret := _m.Called(ctx, query)