Cloud migration: Refactor get by org ID to dashboard svc (#98646)

This commit is contained in:
Stephanie Hingtgen 2025-01-07 14:33:45 -07:00 committed by GitHub
parent 9b1ecaedda
commit 58ed8a9ec2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 167 additions and 26 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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{

View File

@ -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 {

View File

@ -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)

View File

@ -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)