mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Refactor service to make it injectable by wire (#44588)
* Add providers to folder and dashboard services * Refactor folder and dashboard services * Move store implementation to its own file due wire cannot allow us to cast to SQLStore * Add store in some places and more missing dependencies * Bad merge fix * Remove old functions from tests and few fixes * Fix provisioning * Remove store from http server and some test fixes * Test fixes * Fix dashboard and folder tests * Fix library tests * Fix provisioning tests * Fix plugins manager tests * Fix alert and org users tests * Refactor service package and more test fixes * Fix dashboard_test tets * Fix api tests * Some lint fixes * Fix lint * More lint :/ * Move dashboard integration tests to dashboards service and fix dependencies * Lint + tests * More integration tests fixes * Lint * Lint again * Fix tests again and again anda again * Update searchstore_test * Fix goimports * More go imports * More imports fixes * Fix lint * Move UnprovisionDashboard function into dashboard service and remove bus * Use search service instead of bus * Fix test * Fix go imports * Use nil in tests
This commit is contained in:
@@ -6,9 +6,9 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/dashboards"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
dboards "github.com/grafana/grafana/pkg/dashboards"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ type FileReader struct {
|
||||
}
|
||||
|
||||
// NewDashboardFileReader returns a new filereader based on `config`
|
||||
func NewDashboardFileReader(cfg *config, log log.Logger, store dboards.Store) (*FileReader, error) {
|
||||
func NewDashboardFileReader(cfg *config, log log.Logger, store dashboards.Store) (*FileReader, error) {
|
||||
var path string
|
||||
path, ok := cfg.Options["path"].(string)
|
||||
if !ok {
|
||||
@@ -62,7 +62,7 @@ func NewDashboardFileReader(cfg *config, log log.Logger, store dboards.Store) (*
|
||||
Cfg: cfg,
|
||||
Path: path,
|
||||
log: log,
|
||||
dashboardProvisioningService: dashboards.NewProvisioningService(store),
|
||||
dashboardProvisioningService: dashboardservice.ProvideDashboardService(store),
|
||||
FoldersFromFilesStructure: foldersFromFilesStructure,
|
||||
usageTracker: newUsageTracker(),
|
||||
}, nil
|
||||
|
||||
@@ -2,8 +2,6 @@ package dashboards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -11,13 +9,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
dboards "github.com/grafana/grafana/pkg/dashboards"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -28,10 +25,9 @@ const (
|
||||
containingID = "testdata/test-dashboards/containing-id"
|
||||
unprovision = "testdata/test-dashboards/unprovision"
|
||||
foldersFromFilesStructure = "testdata/test-dashboards/folders-from-files-structure"
|
||||
configName = "default"
|
||||
)
|
||||
|
||||
var fakeService *fakeDashboardProvisioningService
|
||||
|
||||
func TestCreatingNewDashboardFileReader(t *testing.T) {
|
||||
setup := func() *config {
|
||||
return &config{
|
||||
@@ -98,17 +94,14 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
logger := log.New("test.logger")
|
||||
cfg := &config{}
|
||||
|
||||
origNewDashboardProvisioningService := dashboards.NewProvisioningService
|
||||
defer func() {
|
||||
dashboards.NewProvisioningService = origNewDashboardProvisioningService
|
||||
}()
|
||||
fakeService := &dashboards.FakeDashboardProvisioning{}
|
||||
defer fakeService.AssertExpectations(t)
|
||||
|
||||
setup := func() {
|
||||
bus.ClearBusHandlers()
|
||||
fakeService = mockDashboardProvisioningService()
|
||||
bus.AddHandler("test", mockGetDashboardQuery)
|
||||
cfg = &config{
|
||||
Name: "Default",
|
||||
Name: configName,
|
||||
Type: "file",
|
||||
OrgID: 1,
|
||||
Folder: "",
|
||||
@@ -122,45 +115,38 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
cfg.Options["path"] = defaultDashboards
|
||||
cfg.Folder = "Team A"
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(nil, nil).Once()
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{Id: 1}, nil).Once()
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{Id: 2}, nil).Times(2)
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
folders := 0
|
||||
dashboards := 0
|
||||
|
||||
for _, i := range fakeService.inserted {
|
||||
if i.Dashboard.IsFolder {
|
||||
folders++
|
||||
} else {
|
||||
dashboards++
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, folders, 1)
|
||||
require.Equal(t, dashboards, 2)
|
||||
})
|
||||
|
||||
t.Run("Can read default dashboard and replace old version in database", func(t *testing.T) {
|
||||
setup()
|
||||
cfg.Options["path"] = oneDashboard
|
||||
|
||||
stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
|
||||
|
||||
fakeService.getDashboard = append(fakeService.getDashboard, &models.Dashboard{
|
||||
Updated: stat.ModTime().AddDate(0, 0, -1),
|
||||
Slug: "grafana",
|
||||
})
|
||||
inserted := 0
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(nil, nil).Once()
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&models.Dashboard{}, nil).Once().
|
||||
Run(func(args mock.Arguments) {
|
||||
inserted++
|
||||
})
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(fakeService.inserted), 1)
|
||||
assert.Equal(t, inserted, 1)
|
||||
})
|
||||
|
||||
t.Run("Dashboard with older timestamp and the same checksum will not replace imported dashboard", func(t *testing.T) {
|
||||
@@ -179,23 +165,23 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
checksum, err := util.Md5Sum(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
|
||||
"Default": {
|
||||
{
|
||||
Name: "Default",
|
||||
ExternalId: absPath,
|
||||
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
|
||||
CheckSum: checksum,
|
||||
},
|
||||
provisionedDashboard := []*models.DashboardProvisioning{
|
||||
{
|
||||
Name: "Default",
|
||||
ExternalId: absPath,
|
||||
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
|
||||
CheckSum: checksum,
|
||||
},
|
||||
}
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(fakeService.inserted), 0)
|
||||
})
|
||||
|
||||
t.Run("Dashboard with older timestamp and different checksum will replace imported dashboard", func(t *testing.T) {
|
||||
@@ -206,23 +192,24 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
|
||||
"Default": {
|
||||
{
|
||||
Name: "Default",
|
||||
ExternalId: absPath,
|
||||
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
|
||||
CheckSum: "fakechecksum",
|
||||
},
|
||||
provisionedDashboard := []*models.DashboardProvisioning{
|
||||
{
|
||||
Name: "Default",
|
||||
ExternalId: absPath,
|
||||
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
|
||||
CheckSum: "fakechecksum",
|
||||
},
|
||||
}
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(fakeService.inserted), 1)
|
||||
})
|
||||
|
||||
t.Run("Dashboard with newer timestamp and the same checksum will not replace imported dashboard", func(t *testing.T) {
|
||||
@@ -241,23 +228,23 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
checksum, err := util.Md5Sum(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
|
||||
"Default": {
|
||||
{
|
||||
Name: "Default",
|
||||
ExternalId: absPath,
|
||||
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
|
||||
CheckSum: checksum,
|
||||
},
|
||||
provisionedDashboard := []*models.DashboardProvisioning{
|
||||
{
|
||||
Name: "Default",
|
||||
ExternalId: absPath,
|
||||
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
|
||||
CheckSum: checksum,
|
||||
},
|
||||
}
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(fakeService.inserted), 0)
|
||||
})
|
||||
|
||||
t.Run("Dashboard with newer timestamp and different checksum should replace imported dashboard", func(t *testing.T) {
|
||||
@@ -268,36 +255,39 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
|
||||
"Default": {
|
||||
{
|
||||
Name: "Default",
|
||||
ExternalId: absPath,
|
||||
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
|
||||
CheckSum: "fakechecksum",
|
||||
},
|
||||
provisionedDashboard := []*models.DashboardProvisioning{
|
||||
{
|
||||
Name: "Default",
|
||||
ExternalId: absPath,
|
||||
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
|
||||
CheckSum: "fakechecksum",
|
||||
},
|
||||
}
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(fakeService.inserted), 1)
|
||||
})
|
||||
|
||||
t.Run("Overrides id from dashboard.json files", func(t *testing.T) {
|
||||
setup()
|
||||
cfg.Options["path"] = containingID
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(nil, nil).Once()
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(fakeService.inserted), 1)
|
||||
})
|
||||
|
||||
t.Run("Get folder from files structure", func(t *testing.T) {
|
||||
@@ -305,40 +295,16 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
cfg.Options["path"] = foldersFromFilesStructure
|
||||
cfg.Options["foldersFromFilesStructure"] = true
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(nil, nil).Once()
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(3)
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(fakeService.inserted), 5)
|
||||
|
||||
foldersCount := 0
|
||||
for _, d := range fakeService.inserted {
|
||||
if d.Dashboard.IsFolder {
|
||||
foldersCount++
|
||||
}
|
||||
}
|
||||
require.Equal(t, foldersCount, 2)
|
||||
|
||||
foldersAndDashboards := make(map[string]struct{}, 5)
|
||||
for _, d := range fakeService.inserted {
|
||||
title := d.Dashboard.Title
|
||||
if _, ok := foldersAndDashboards[title]; ok {
|
||||
require.Nil(t, fmt.Errorf("dashboard title %q already exists", title))
|
||||
}
|
||||
|
||||
switch title {
|
||||
case "folderOne", "folderTwo":
|
||||
require.True(t, d.Dashboard.IsFolder)
|
||||
case "Grafana1", "Grafana2", "RootDashboard":
|
||||
require.False(t, d.Dashboard.IsFolder)
|
||||
default:
|
||||
require.Nil(t, fmt.Errorf("unknown dashboard title %q", title))
|
||||
}
|
||||
|
||||
foldersAndDashboards[title] = struct{}{}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Invalid configuration should return error", func(t *testing.T) {
|
||||
@@ -367,30 +333,23 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
cfg1 := &config{Name: "1", Type: "file", OrgID: 1, Folder: "f1", Options: map[string]interface{}{"path": containingID}}
|
||||
cfg2 := &config{Name: "2", Type: "file", OrgID: 1, Folder: "f2", Options: map[string]interface{}{"path": containingID}}
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return(nil, nil).Times(2)
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
|
||||
|
||||
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
|
||||
reader1.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader1.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
|
||||
reader2.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader2.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
var folderCount int
|
||||
var dashCount int
|
||||
for _, o := range fakeService.inserted {
|
||||
if o.Dashboard.IsFolder {
|
||||
folderCount++
|
||||
} else {
|
||||
dashCount++
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, folderCount, 2)
|
||||
require.Equal(t, dashCount, 2)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -422,16 +381,9 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
|
||||
_, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
|
||||
require.NoError(t, err)
|
||||
inserted := false
|
||||
for _, d := range fakeService.inserted {
|
||||
if d.Dashboard.IsFolder && d.Dashboard.Id == folderID {
|
||||
inserted = true
|
||||
}
|
||||
}
|
||||
require.Equal(t, len(fakeService.inserted), 1)
|
||||
require.True(t, inserted)
|
||||
})
|
||||
|
||||
t.Run("Walking the folder with dashboards", func(t *testing.T) {
|
||||
@@ -456,56 +408,53 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
absPath2, err := filepath.Abs(unprovision + "/dashboard2.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
provisionedDashboard := []*models.DashboardProvisioning{
|
||||
{DashboardId: 1, Name: "Default", ExternalId: absPath1},
|
||||
{DashboardId: 2, Name: "Default", ExternalId: absPath2},
|
||||
}
|
||||
|
||||
setupFakeService := func() {
|
||||
setup()
|
||||
cfg = &config{
|
||||
Name: "Default",
|
||||
Name: configName,
|
||||
Type: "file",
|
||||
OrgID: 1,
|
||||
Options: map[string]interface{}{
|
||||
"folder": unprovision,
|
||||
},
|
||||
}
|
||||
|
||||
fakeService.inserted = []*dashboards.SaveDashboardDTO{
|
||||
{Dashboard: &models.Dashboard{Id: 1}},
|
||||
{Dashboard: &models.Dashboard{Id: 2}},
|
||||
}
|
||||
|
||||
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
|
||||
"Default": {
|
||||
{DashboardId: 1, Name: "Default", ExternalId: absPath1},
|
||||
{DashboardId: 2, Name: "Default", ExternalId: absPath2},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("Missing dashboard should be unprovisioned if DisableDeletion = true", func(t *testing.T) {
|
||||
setupFakeService()
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
|
||||
fakeService.On("UnprovisionDashboard", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
|
||||
|
||||
cfg.DisableDeletion = true
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(fakeService.provisioned["Default"]), 1)
|
||||
require.Equal(t, fakeService.provisioned["Default"][0].ExternalId, absPath1)
|
||||
})
|
||||
|
||||
t.Run("Missing dashboard should be deleted if DisableDeletion = false", func(t *testing.T) {
|
||||
setupFakeService()
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
|
||||
fakeService.On("DeleteProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
reader.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.walkDisk(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(fakeService.provisioned["Default"]), 1)
|
||||
require.Equal(t, fakeService.provisioned["Default"][0].ExternalId, absPath1)
|
||||
require.Equal(t, len(fakeService.inserted), 1)
|
||||
require.Equal(t, fakeService.inserted[0].Dashboard.Id, int64(1))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -539,111 +488,6 @@ func (ffi FakeFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func mockDashboardProvisioningService() *fakeDashboardProvisioningService {
|
||||
mock := fakeDashboardProvisioningService{
|
||||
provisioned: map[string][]*models.DashboardProvisioning{},
|
||||
}
|
||||
dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
|
||||
return &mock
|
||||
}
|
||||
return &mock
|
||||
}
|
||||
|
||||
type fakeDashboardProvisioningService struct {
|
||||
dashboards.DashboardProvisioningService
|
||||
|
||||
inserted []*dashboards.SaveDashboardDTO
|
||||
provisioned map[string][]*models.DashboardProvisioning
|
||||
getDashboard []*models.Dashboard
|
||||
}
|
||||
|
||||
func (s *fakeDashboardProvisioningService) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||
if _, ok := s.provisioned[name]; !ok {
|
||||
s.provisioned[name] = []*models.DashboardProvisioning{}
|
||||
}
|
||||
|
||||
return s.provisioned[name], nil
|
||||
}
|
||||
|
||||
func (s *fakeDashboardProvisioningService) SaveProvisionedDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO,
|
||||
provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
// Copy the structs as we need to change them but do not want to alter outside world.
|
||||
var copyProvisioning = &models.DashboardProvisioning{}
|
||||
*copyProvisioning = *provisioning
|
||||
|
||||
var copyDto = &dashboards.SaveDashboardDTO{}
|
||||
*copyDto = *dto
|
||||
|
||||
if copyDto.Dashboard.Id == 0 {
|
||||
copyDto.Dashboard.Id = rand.Int63n(1000000)
|
||||
} else {
|
||||
err := s.DeleteProvisionedDashboard(context.Background(), dto.Dashboard.Id, dto.Dashboard.OrgId)
|
||||
// Lets delete existing so we do not have duplicates
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
s.inserted = append(s.inserted, dto)
|
||||
|
||||
if _, ok := s.provisioned[provisioning.Name]; !ok {
|
||||
s.provisioned[provisioning.Name] = []*models.DashboardProvisioning{}
|
||||
}
|
||||
|
||||
for _, val := range s.provisioned[provisioning.Name] {
|
||||
if val.DashboardId == dto.Dashboard.Id && val.Name == provisioning.Name {
|
||||
// Do not insert duplicates
|
||||
return dto.Dashboard, nil
|
||||
}
|
||||
}
|
||||
|
||||
copyProvisioning.DashboardId = copyDto.Dashboard.Id
|
||||
|
||||
s.provisioned[provisioning.Name] = append(s.provisioned[provisioning.Name], copyProvisioning)
|
||||
return dto.Dashboard, nil
|
||||
}
|
||||
|
||||
func (s *fakeDashboardProvisioningService) SaveFolderForProvisionedDashboards(ctx context.Context, dto *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
s.inserted = append(s.inserted, dto)
|
||||
return dto.Dashboard, nil
|
||||
}
|
||||
|
||||
func (s *fakeDashboardProvisioningService) UnprovisionDashboard(ctx context.Context, dashboardID int64) error {
|
||||
for key, val := range s.provisioned {
|
||||
for index, dashboard := range val {
|
||||
if dashboard.DashboardId == dashboardID {
|
||||
s.provisioned[key] = append(s.provisioned[key][:index], s.provisioned[key][index+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fakeDashboardProvisioningService) DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error {
|
||||
err := s.UnprovisionDashboard(ctx, dashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for index, val := range s.inserted {
|
||||
if val.Dashboard.Id == dashboardID {
|
||||
s.inserted = append(s.inserted[:index], s.inserted[util.MinInt(index+1, len(s.inserted)):]...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fakeDashboardProvisioningService) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func mockGetDashboardQuery(ctx context.Context, cmd *models.GetDashboardQuery) error {
|
||||
for _, d := range fakeService.getDashboard {
|
||||
if d.Slug == cmd.Slug {
|
||||
cmd.Result = d
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func mockGetDashboardQuery(_ context.Context, _ *models.GetDashboardQuery) error {
|
||||
return models.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -18,7 +20,8 @@ const (
|
||||
|
||||
func TestDuplicatesValidator(t *testing.T) {
|
||||
bus.ClearBusHandlers()
|
||||
fakeService = mockDashboardProvisioningService()
|
||||
fakeService := &dashboards.FakeDashboardProvisioning{}
|
||||
defer fakeService.AssertExpectations(t)
|
||||
|
||||
bus.AddHandler("test", mockGetDashboardQuery)
|
||||
cfg := &config{
|
||||
@@ -32,6 +35,11 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
|
||||
t.Run("Duplicates validator should collect info about duplicate UIDs and titles within folders", func(t *testing.T) {
|
||||
const folderName = "duplicates-validator-folder"
|
||||
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(3)
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(2)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
|
||||
|
||||
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -47,9 +55,11 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
}
|
||||
|
||||
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
|
||||
reader1.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
|
||||
reader2.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2})
|
||||
@@ -79,6 +89,11 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
|
||||
t.Run("Duplicates validator should not collect info about duplicate UIDs and titles within folders for different orgs", func(t *testing.T) {
|
||||
const folderName = "duplicates-validator-folder"
|
||||
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(3)
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(2)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
|
||||
|
||||
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -94,9 +109,11 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
}
|
||||
|
||||
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
|
||||
reader1.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
|
||||
reader2.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2})
|
||||
@@ -135,6 +152,10 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Duplicates validator should restrict write access only for readers with duplicates", func(t *testing.T) {
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(5)
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(3)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(6)
|
||||
|
||||
cfg1 := &config{
|
||||
Name: "first", Type: "file", OrgID: 1, Folder: "duplicates-validator-folder",
|
||||
Options: map[string]interface{}{"path": twoDashboardsWithUID},
|
||||
@@ -149,12 +170,15 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
}
|
||||
|
||||
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
|
||||
reader1.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
|
||||
reader2.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
reader3, err := NewDashboardFileReader(cfg3, logger, nil)
|
||||
reader3.dashboardProvisioningService = fakeService
|
||||
require.NoError(t, err)
|
||||
|
||||
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2, reader3})
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
plugifaces "github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/encryption"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
|
||||
@@ -20,10 +21,9 @@ import (
|
||||
)
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginStore plugifaces.Store,
|
||||
encryptionService encryption.Internal, notificatonService *notifications.NotificationService) (*ProvisioningServiceImpl, error) {
|
||||
encryptionService encryption.Internal, notificatonService *notifications.NotificationService, dashboardsStore dashboardservice.Store) (*ProvisioningServiceImpl, error) {
|
||||
s := &ProvisioningServiceImpl{
|
||||
Cfg: cfg,
|
||||
SQLStore: sqlStore,
|
||||
pluginStore: pluginStore,
|
||||
EncryptionService: encryptionService,
|
||||
NotificationService: notificatonService,
|
||||
@@ -32,6 +32,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginStore p
|
||||
provisionNotifiers: notifiers.Provision,
|
||||
provisionDatasources: datasources.Provision,
|
||||
provisionPlugins: plugins.Provision,
|
||||
dashboardsStore: dashboardsStore,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -88,6 +89,7 @@ type ProvisioningServiceImpl struct {
|
||||
provisionDatasources func(context.Context, string) error
|
||||
provisionPlugins func(context.Context, string, plugifaces.Store) error
|
||||
mutex sync.Mutex
|
||||
dashboardsStore dashboardservice.Store
|
||||
}
|
||||
|
||||
func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) error {
|
||||
@@ -170,7 +172,7 @@ func (ps *ProvisioningServiceImpl) ProvisionNotifications(ctx context.Context) e
|
||||
|
||||
func (ps *ProvisioningServiceImpl) ProvisionDashboards(ctx context.Context) error {
|
||||
dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards")
|
||||
dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.SQLStore)
|
||||
dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.dashboardsStore)
|
||||
if err != nil {
|
||||
return errutil.Wrap("Failed to create provisioner", err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dboards "github.com/grafana/grafana/pkg/dashboards"
|
||||
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -92,7 +92,7 @@ func setup() *serviceTestStruct {
|
||||
}
|
||||
|
||||
serviceTest.service = newProvisioningServiceImpl(
|
||||
func(context.Context, string, dboards.Store) (dashboards.DashboardProvisioner, error) {
|
||||
func(context.Context, string, dashboardstore.Store) (dashboards.DashboardProvisioner, error) {
|
||||
return serviceTest.mock, nil
|
||||
},
|
||||
nil,
|
||||
|
||||
Reference in New Issue
Block a user