K8s: Remove kubernetesClientDashboardsFolders feature flag (#108626)
Actionlint / Lint GitHub Actions files (push) Waiting to run Details
Backend Code Checks / Validate Backend Configs (push) Waiting to run Details
Backend Unit Tests / Detect whether code changed (push) Waiting to run Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (1/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (2/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (3/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (4/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (5/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (6/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (7/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (8/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (1/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (2/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (3/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (4/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (5/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (6/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (7/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (8/8) (push) Blocked by required conditions Details
Backend Unit Tests / All backend unit tests complete (push) Blocked by required conditions Details
CodeQL checks / Analyze (actions) (push) Waiting to run Details
CodeQL checks / Analyze (go) (push) Waiting to run Details
CodeQL checks / Analyze (javascript) (push) Waiting to run Details
Lint Frontend / Detect whether code changed (push) Waiting to run Details
Lint Frontend / Lint (push) Blocked by required conditions Details
Lint Frontend / Typecheck (push) Blocked by required conditions Details
Lint Frontend / Betterer (push) Blocked by required conditions Details
golangci-lint / lint-go (push) Waiting to run Details
Verify i18n / verify-i18n (push) Waiting to run Details
Documentation / Build & Verify Docs (push) Waiting to run Details
End-to-end tests / Detect whether code changed (push) Waiting to run Details
End-to-end tests / Build & Package Grafana (push) Blocked by required conditions Details
End-to-end tests / Build E2E test runner (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (--flags="--env dashboardScene=false", e2e/old-arch/dashboards-suite, dashboards-suite (old arch)) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (--flags="--env dashboardScene=false", e2e/old-arch/panels-suite, panels-suite (old arch)) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (--flags="--env dashboardScene=false", e2e/old-arch/smoke-tests-suite, smoke-tests-suite (old arch)) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (--flags="--env dashboardScene=false", e2e/old-arch/various-suite, various-suite (old arch)) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (e2e/dashboards-suite, dashboards-suite) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (e2e/panels-suite, panels-suite) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (e2e/various-suite, various-suite) (push) Blocked by required conditions Details
End-to-end tests / Verify Storybook (Playwright) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (1, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (2, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (3, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (4, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (5, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (6, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (7, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (8, 8) (push) Blocked by required conditions Details
End-to-end tests / All Playwright tests complete (push) Blocked by required conditions Details
End-to-end tests / A11y test (push) Blocked by required conditions Details
End-to-end tests / All E2E tests complete (push) Blocked by required conditions Details
Frontend tests / Detect whether code changed (push) Waiting to run Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (1) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (2) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (3) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (4) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (5) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (6) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (7) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (8) (push) Blocked by required conditions Details
Frontend tests / Decoupled plugin tests (push) Blocked by required conditions Details
Frontend tests / All frontend unit tests complete (push) Blocked by required conditions Details
Integration Tests / Sqlite (${{ matrix.shard }}) (1/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (2/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (3/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (4/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (5/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (6/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (7/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (8/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (1/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (2/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (3/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (4/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (5/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (6/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (7/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (8/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (1/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (2/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (3/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (4/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (5/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (6/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (7/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (8/8) (push) Waiting to run Details
Integration Tests / All backend integration tests complete (push) Blocked by required conditions Details
publish-technical-documentation-next / sync (push) Waiting to run Details
Reject GitHub secrets / reject-gh-secrets (push) Waiting to run Details
Build Release Packages / setup (push) Waiting to run Details
Build Release Packages / Dispatch grafana-enterprise build (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:darwin/amd64, darwin-amd64, true) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:darwin/arm64, darwin-arm64, true) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/amd64,deb:grafana:linux/amd64,rpm:grafana:linux/amd64,docker:grafana:linux/amd64,docker:grafana:linux/amd64:ubuntu,npm:grafana,storybook, linux-amd64, true) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/arm/v6,deb:grafana:linux/arm/v6, linux-armv6, true) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/arm/v7,deb:grafana:linux/arm/v7,docker:grafana:linux/arm/v7,docker:grafana:linux/arm/v7:ubuntu, linux-armv7, true) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/arm64,deb:grafana:linux/arm64,rpm:grafana:linux/arm64,docker:grafana:linux/arm64,docker:grafana:linux/arm64:ubuntu, linux-arm64, false) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/s390x,deb:grafana:linux/s390x,rpm:grafana:linux/s390x,docker:grafana:linux/s390x,docker:grafana:linux/s390x:ubuntu, linux-s390x, true) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:windows/amd64,zip:grafana:windows/amd64,msi:grafana:windows/amd64, windows-amd64, true) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:windows/arm64,zip:grafana:windows/arm64, windows-arm64, true) (push) Blocked by required conditions Details
Build Release Packages / Upload artifacts (push) Blocked by required conditions Details
Run dashboard schema v2 e2e / dashboard-schema-v2-e2e (push) Waiting to run Details
Shellcheck / Shellcheck scripts (push) Waiting to run Details
Run Storybook a11y tests / Detect whether code changed (push) Waiting to run Details
Run Storybook a11y tests / Run Storybook a11y tests (push) Blocked by required conditions Details
Swagger generated code / Verify committed API specs match (push) Waiting to run Details
Dispatch sync to mirror / dispatch-job (push) Waiting to run Details

This commit is contained in:
Stephanie Hingtgen 2025-07-29 16:52:57 -05:00 committed by GitHub
parent e3cb84bef8
commit 1f025fe1a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 1585 additions and 3235 deletions

View File

@ -43,7 +43,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `dashgpt` | Enable AI powered features in dashboards | Yes |
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
| `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | Yes |

View File

@ -10,7 +10,6 @@ kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 0

View File

@ -10,7 +10,6 @@ kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 1

View File

@ -10,7 +10,6 @@ kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 2

View File

@ -10,7 +10,6 @@ kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 2

View File

@ -10,7 +10,6 @@ kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 3

View File

@ -10,7 +10,6 @@ kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 4

View File

@ -10,7 +10,6 @@ kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 5

View File

@ -290,11 +290,6 @@ export interface FeatureToggles {
*/
kubernetesDashboards?: boolean;
/**
* Route the folder and dashboard service requests to k8s
* @default true
*/
kubernetesClientDashboardsFolders?: boolean;
/**
* Disable schema validation for dashboards/v1
*/
dashboardDisableSchemaValidationV1?: boolean;

View File

@ -204,7 +204,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
}
metrics.MFolderIDsAPICount.WithLabelValues(metrics.GetDashboard).Inc()
// lookup folder title & url
if dash.FolderUID != "" && hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
if dash.FolderUID != "" {
queryResult, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: c.GetOrgID(),
UID: &dash.FolderUID,

View File

@ -19,32 +19,23 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/serverlock"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/client"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/dashboards/service"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/dashboardversion/dashvertest"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
libraryelementsfake "github.com/grafana/grafana/pkg/services/libraryelements/fake"
"github.com/grafana/grafana/pkg/services/librarypanels"
@ -59,14 +50,10 @@ import (
"github.com/grafana/grafana/pkg/services/publicdashboards/api"
publicdashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/star/startest"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/web"
"github.com/grafana/grafana/pkg/web/webtest"
)
@ -712,25 +699,42 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
t.Run("Given provisioned dashboard", func(t *testing.T) {
mockSQLStore := dbtest.NewFakeDB()
dashboardStore := dashboards.NewFakeDashboardStore(t)
dashboardStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioningSearchResults{ExternalID: "/dashboard1.json"}, nil).Once()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardProvisioningService := dashboards.NewFakeDashboardProvisioning(t)
dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "editable": true, "style": "dark"}`))
require.NoError(t, err)
qResult := &dashboards.Dashboard{ID: 1, Data: dataValue}
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil)
dashboardProvisioningService.On("GetProvisionedDashboardDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{ExternalID: "/dashboard1.json"}, nil)
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", org.RoleEditor, func(sc *scenarioContext) {
fakeProvisioningService := provisioning.NewProvisioningServiceMock(context.Background())
fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
return "/tmp/grafana/dashboards"
}
dash := getDashboardShouldReturn200WithConfig(t, sc, fakeProvisioningService, dashboardStore, dashboardService, nil)
hs := &HTTPServer{
Cfg: setting.NewCfg(),
ProvisioningService: fakeProvisioningService,
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &libraryelementsfake.LibraryElementService{},
dashboardProvisioningService: dashboardProvisioningService,
SQLStore: mockSQLStore,
AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true},
DashboardService: dashboardService,
Features: featuremgmt.WithFeatures(),
starService: startest.NewStarServiceFake(),
tracer: tracing.InitializeTracerForTest(),
}
hs.callGetDashboard(sc)
assert.Equal(t, "../../../dashboard1.json", dash.Meta.ProvisionedExternalId, mockSQLStore)
assert.Equal(t, http.StatusOK, sc.resp.Code)
dash := dtos.DashboardFullWithMeta{}
err := json.NewDecoder(sc.resp.Body).Decode(&dash)
require.NoError(t, err)
assert.Equal(t, "../../../dashboard1.json", dash.Meta.ProvisionedExternalId)
}, mockSQLStore)
loggedInUserScenarioWithRole(t, "When allowUiUpdates is true and calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", org.RoleEditor, func(sc *scenarioContext) {
@ -748,7 +752,7 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
ProvisioningService: fakeProvisioningService,
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &libraryelementsfake.LibraryElementService{},
dashboardProvisioningService: mockDashboardProvisioningService{},
dashboardProvisioningService: dashboardProvisioningService,
SQLStore: mockSQLStore,
AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true},
DashboardService: dashboardService,
@ -771,6 +775,7 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
t.Run("v2 dashboards should not be returned in api", func(t *testing.T) {
mockSQLStore := dbtest.NewFakeDB()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardProvisioningService := dashboards.NewFakeDashboardProvisioning(t)
dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "apiVersion": "v2"}`))
require.NoError(t, err)
@ -794,7 +799,7 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
Features: featuremgmt.WithFeatures(),
starService: startest.NewStarServiceFake(),
tracer: tracing.InitializeTracerForTest(),
dashboardProvisioningService: mockDashboardProvisioningService{},
dashboardProvisioningService: dashboardProvisioningService,
folderService: foldertest.NewFakeService(),
log: log.New("test"),
namespacer: func(orgID int64) string { return strconv.FormatInt(orgID, 10) },
@ -912,79 +917,6 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) {
}, mockSQLStore)
}
func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, provisioningService provisioning.ProvisioningService, dashboardStore dashboards.Store, dashboardService dashboards.DashboardService, folderStore folder.FolderStore) dtos.DashboardFullWithMeta {
t.Helper()
if provisioningService == nil {
provisioningService = provisioning.NewProvisioningServiceMock(context.Background())
}
features := featuremgmt.WithFeatures()
var err error
if dashboardStore == nil {
sql, cfg := db.InitTestDBWithCfg(t)
dashboardStore, err = database.ProvideDashboardStore(sql, cfg, features, tagimpl.ProvideService(sql))
require.NoError(t, err)
}
libraryPanelsService := mockLibraryPanelService{}
libraryElementsService := libraryelementsfake.LibraryElementService{}
cfg := setting.NewCfg()
ac := accesscontrolmock.New()
folderPermissions := accesscontrolmock.NewMockedPermissionsService()
dashboardPermissions := accesscontrolmock.NewMockedPermissionsService()
db := db.InitTestDB(t)
fStore := folderimpl.ProvideStore(db)
quotaService := quotatest.New(false, nil)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil,
dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
if dashboardService == nil {
dashboardService, err = service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, features, folderPermissions,
ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil,
dualwrite.ProvideTestService(), sort.ProvideService(),
serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(),
)
require.NoError(t, err)
dashboardService.(dashboards.PermissionsRegistrationService).RegisterDashboardPermissions(dashboardPermissions)
}
dashboardProvisioningService, err := service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, features, folderPermissions,
ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil,
dualwrite.ProvideTestService(), sort.ProvideService(),
serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(),
)
require.NoError(t, err)
hs := &HTTPServer{
Cfg: cfg,
LibraryPanelService: &libraryPanelsService,
LibraryElementService: &libraryElementsService,
SQLStore: sc.sqlStore,
ProvisioningService: provisioningService,
AccessControl: accesscontrolmock.New(),
dashboardProvisioningService: dashboardProvisioningService,
DashboardService: dashboardService,
Features: featuremgmt.WithFeatures(),
starService: startest.NewStarServiceFake(),
tracer: tracing.InitializeTracerForTest(),
}
hs.callGetDashboard(sc)
require.Equal(sc.t, 200, sc.resp.Code)
dash := dtos.DashboardFullWithMeta{}
err = json.NewDecoder(sc.resp.Body).Decode(&dash)
require.NoError(sc.t, err)
return dash
}
func (hs *HTTPServer) callGetDashboard(sc *scenarioContext) {
sc.handlerFunc = hs.GetDashboard
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()

View File

@ -1,17 +1,14 @@
package api
import (
"context"
"errors"
"net/http"
"strconv"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/api/apierrors"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/services/accesscontrol"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
@ -20,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
@ -198,13 +194,6 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response
return apierrors.ToFolderErrorResponse(err)
}
// Only set default permissions if the Folder API Server is disabled.
if !hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
if err := hs.setDefaultFolderPermissions(c.Req.Context(), cmd.OrgID, cmd.SignedInUser, folder); err != nil {
hs.log.Error("Could not set the default folder permissions", "folder", folder.Title, "user", cmd.SignedInUser, "error", err)
}
}
folderDTO, err := hs.newToFolderDto(c, folder)
if err != nil {
return response.Err(err)
@ -214,46 +203,6 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response
return response.JSON(http.StatusOK, folderDTO)
}
func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int64, user identity.Requester, folder *folder.Folder) error {
if !hs.Cfg.RBAC.PermissionsOnCreation("folder") {
return nil
}
var permissions []accesscontrol.SetResourcePermissionCommand
if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) {
userID, err := user.GetInternalID()
if err != nil {
return err
}
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
})
}
isNested := folder.ParentUID != ""
if !isNested || !hs.Features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
}...)
}
_, err := hs.folderPermissionsService.SetPermissions(ctx, orgID, folder.UID, permissions...)
if err != nil {
return err
}
if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) {
// Clear permission cache for the user who's created the folder, so that new permissions are fetched for their next call
// Required for cases when caller wants to immediately interact with the newly created object
hs.accesscontrolService.ClearUserPermissionCache(user)
}
return nil
}
// swagger:route POST /folders/{folder_uid}/move folders moveFolder
//
// Move folder.

View File

@ -620,9 +620,6 @@ func TestGetFolderLegacyAndUnifiedStorage(t *testing.T) {
}
featuresArr := []any{featuremgmt.FlagNestedFolders}
if tc.unifiedStorageEnabled {
featuresArr = append(featuresArr, featuremgmt.FlagKubernetesClientDashboardsFolders)
}
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = cfg
@ -676,39 +673,6 @@ func TestGetFolderLegacyAndUnifiedStorage(t *testing.T) {
}
func TestSetDefaultPermissionsWhenCreatingFolder(t *testing.T) {
folderService := &foldertest.FakeService{}
folderWithoutParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\"}"
type testCase struct {
description string
expectedCallsToSetPermissions int
expectedCode int
expectedFolder *folder.Folder
permissions []accesscontrol.Permission
featuresArr []any
input string
}
tcs := []testCase{
{
description: "folder creation succeeds, via legacy storage",
expectedCallsToSetPermissions: 1,
input: folderWithoutParentInput,
expectedCode: http.StatusOK,
expectedFolder: &folder.Folder{UID: "uid", Title: "Folder"},
permissions: []accesscontrol.Permission{{Action: dashboards.ActionFoldersCreate}},
},
{
description: "folder creation succeeds, via API Server",
expectedCallsToSetPermissions: 0,
input: folderWithoutParentInput,
expectedCode: http.StatusOK,
expectedFolder: &folder.Folder{UID: "uid", Title: "Folder"},
permissions: []accesscontrol.Permission{{Action: dashboards.ActionFoldersCreate}},
featuresArr: []any{featuremgmt.FlagKubernetesClientDashboardsFolders},
},
}
// we need to save these values because they are defined at `setting` package level
// and modified when we invoke setting.NewCfgFromINIFile
prevCookieSameSiteDisabled := setting.CookieSameSiteDisabled
@ -725,42 +689,32 @@ func TestSetDefaultPermissionsWhenCreatingFolder(t *testing.T) {
setting.CookieSameSiteDisabled = prevCookieSameSiteDisabled
setting.CookieSameSiteMode = prevCookieSameSiteMode
for _, tc := range tcs {
t.Run(tc.description, func(t *testing.T) {
folderService.ExpectedFolder = tc.expectedFolder
folderService := &foldertest.FakeService{
ExpectedFolder: &folder.Folder{UID: "uid", Title: "Folder"},
}
folderPermService := acmock.NewMockedPermissionsService()
folderPermService.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
srv := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = cfg
featuresArr := append(tc.featuresArr, featuremgmt.FlagNestedFolders)
hs.Features = featuremgmt.WithFeatures(
featuresArr...,
)
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
hs.folderService = folderService
hs.folderPermissionsService = folderPermService
hs.accesscontrolService = actest.FakeService{}
})
input := strings.NewReader(tc.input)
input := strings.NewReader("{ \"uid\": \"uid\", \"title\": \"Folder\"}")
req := srv.NewPostRequest("/api/folders", input)
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, tc.permissions))
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, []accesscontrol.Permission{{Action: dashboards.ActionFoldersCreate}}))
resp, err := srv.SendJSON(req)
require.NoError(t, err)
require.Equal(t, tc.expectedCode, resp.StatusCode)
require.Equal(t, http.StatusOK, resp.StatusCode)
folder := dtos.Folder{}
err = json.NewDecoder(resp.Body).Decode(&folder)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
folderPermService.AssertNumberOfCalls(t, "SetPermissions", tc.expectedCallsToSetPermissions)
if tc.expectedCode == http.StatusOK {
assert.Equal(t, "uid", folder.UID)
assert.Equal(t, "Folder", folder.Title)
}
})
}
}

View File

@ -872,11 +872,9 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool, cfgOverrides ...conf
rr := routing.NewRouteRegister()
tracer := tracing.InitializeTracerForTest()
fakeFolder := &folder.Folder{UID: "folderUID", Title: "Folder"}
mockFolder := &foldertest.FakeService{
ExpectedFolders: []*folder.Folder{fakeFolder},
ExpectedFolder: fakeFolder,
}
fakeFolder := &folder.Folder{UID: "folderUID", Title: "Folder", Fullpath: "Folder"}
mockFolder := foldertest.NewFakeService()
mockFolder.AddFolder(fakeFolder)
cfg := setting.NewCfg()
section, err := cfg.Raw.NewSection("cloud_migration")

View File

@ -3,6 +3,7 @@ package cloudmigrationimpl
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
@ -18,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
@ -408,6 +410,9 @@ func updateNotificationPolicyTree(t *testing.T, ctx context.Context, service *Se
func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, isPaused bool, ruleGroup string) models.AlertRule {
t.Helper()
// Ensure the folder exists before creating alert rules
createFolder(t, ctx, service, user, "folderUID", "Test Folder")
rule := models.AlertRule{
OrgID: user.GetOrgID(),
Title: fmt.Sprintf("Alert Rule SLO (Paused: %v) - %v", isPaused, ruleGroup),
@ -437,6 +442,19 @@ func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *
return createdRule
}
func createFolder(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, uid, title string) {
t.Helper()
_, err := service.folderService.Create(ctx, &folder.CreateFolderCommand{
OrgID: user.GetOrgID(),
UID: uid,
Title: title,
SignedInUser: user,
})
if err != nil && !errors.Is(err, dashboards.ErrFolderWithSameUIDExists) {
require.NoError(t, err)
}
}
func createAlertRuleGroup(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, title string, rules []models.AlertRule) models.AlertRuleGroup {
t.Helper()

View File

@ -146,14 +146,6 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb
}
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc()
// in the k8s flow, we connect the library panels in pkg/registry/apis/dashboard/legacy/sql_dashboards.go
if !s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
err = s.libraryPanelService.ConnectLibraryPanelsForDashboard(ctx, req.User, savedDashboard)
if err != nil {
return nil, err
}
}
revision := savedDashboard.Data.Get("revision").MustInt64(0)
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.DashboardImport).Inc()
return &dashboardimport.ImportDashboardResponse{

View File

@ -46,16 +46,11 @@ func TestImportDashboardService(t *testing.T) {
}
importLibraryPanelsForDashboard := false
connectLibraryPanelsForDashboardCalled := false
libraryPanelService := &libraryPanelServiceMock{
importLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64, folderUID string) error {
importLibraryPanelsForDashboard = true
return nil
},
connectLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error {
connectLibraryPanelsForDashboardCalled = true
return nil
},
}
folderService := &foldertest.FakeService{
ExpectedFolder: &folder.Folder{
@ -98,7 +93,6 @@ func TestImportDashboardService(t *testing.T) {
require.Equal(t, "prom", panel.Get("datasource").MustString())
require.True(t, importLibraryPanelsForDashboard)
require.True(t, connectLibraryPanelsForDashboardCalled)
})
t.Run("When importing a non-plugin dashboard should save dashboard and sync library panels", func(t *testing.T) {

View File

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
@ -77,12 +76,8 @@ type DashboardProvisioningService interface {
type Store interface {
DeleteDashboard(ctx context.Context, cmd *DeleteDashboardCommand) error
CleanupAfterDelete(ctx context.Context, cmd *DeleteDashboardCommand) error
DeleteAllDashboards(ctx context.Context, orgID int64) error
DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error
FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error)
GetDashboardUIDByID(ctx context.Context, query *GetDashboardRefByIDQuery) (*DashboardRef, error)
GetDashboards(ctx context.Context, query *GetDashboardsQuery) ([]*Dashboard, error)
// GetDashboardsByPluginID retrieves dashboards identified by plugin.
GetDashboardsByPluginID(ctx context.Context, query *GetDashboardsByPluginIDQuery) ([]*Dashboard, error)
GetDashboardTags(ctx context.Context, query *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error)
@ -97,11 +92,7 @@ type Store interface {
// ValidateDashboardBeforeSave validates a dashboard before save.
ValidateDashboardBeforeSave(ctx context.Context, dashboard *Dashboard, overwrite bool) (bool, error)
Count(context.Context, *quota.ScopeParameters) (*quota.Map, error)
CountInOrg(ctx context.Context, orgID int64, isFolder bool) (int64, error)
// CountDashboardsInFolder returns the number of dashboards associated with
// the given parent folder ID.
CountDashboardsInFolders(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error)
DeleteDashboardsInFolders(ctx context.Context, request *DeleteDashboardsInFolderRequest) error
GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error)

View File

@ -2,10 +2,8 @@ package database
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
"go.opentelemetry.io/otel"
@ -25,7 +23,6 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/star"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/tag"
"github.com/grafana/grafana/pkg/setting"
@ -352,34 +349,6 @@ func (d *dashboardStore) UnprovisionDashboard(ctx context.Context, id int64) err
})
}
func (d *dashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *dashboards.DeleteOrphanedProvisionedDashboardsCommand) error {
ctx, span := tracer.Start(ctx, "dashboards.database.DeleteOrphanedProvisionedDashboards")
defer span.End()
return d.store.WithDbSession(ctx, func(sess *db.Session) error {
var result []*dashboards.DashboardProvisioning
convertedReaderNames := make([]any, len(cmd.ReaderNames))
for index, readerName := range cmd.ReaderNames {
convertedReaderNames[index] = readerName
}
err := sess.NotIn("name", convertedReaderNames...).Find(&result)
if err != nil {
return err
}
for _, deleteDashCommand := range result {
err := d.DeleteDashboard(ctx, &dashboards.DeleteDashboardCommand{ID: deleteDashCommand.DashboardID})
if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {
return err
}
}
return nil
})
}
func (d *dashboardStore) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
ctx, span := tracer.Start(ctx, "dashboards.database.Count")
defer span.End()
@ -690,16 +659,6 @@ func (d *dashboardStore) CleanupAfterDelete(ctx context.Context, cmd *dashboards
return err
}
func (d *dashboardStore) DeleteAllDashboards(ctx context.Context, orgID int64) error {
ctx, span := tracer.Start(ctx, "dashboards.database.DeleteAllDashboards")
defer span.End()
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
_, err := sess.Where("org_id = ?", orgID).Delete(&dashboards.Dashboard{})
return err
})
}
// FIXME: Remove me and handle nested deletions in the service with the DashboardPermissionsService
func (d *dashboardStore) deleteResourcePermissions(sess *db.Session, orgID int64, resourceScope string) error {
// retrieve all permissions for the resource scope and org id
@ -821,58 +780,6 @@ func (d *dashboardStore) GetDashboard(ctx context.Context, query *dashboards.Get
return queryResult, err
}
func (d *dashboardStore) GetDashboardUIDByID(ctx context.Context, query *dashboards.GetDashboardRefByIDQuery) (*dashboards.DashboardRef, error) {
ctx, span := tracer.Start(ctx, "dashboards.database.GetDashboardUIDByID")
defer span.End()
us := &dashboards.DashboardRef{}
err := d.store.WithDbSession(ctx, func(sess *db.Session) error {
var rawSQL = `SELECT uid, slug, folder_uid from dashboard WHERE Id=?`
exists, err := sess.SQL(rawSQL, query.ID).Get(us)
if err != nil {
return err
} else if !exists {
return dashboards.ErrDashboardNotFound
}
return nil
})
if err != nil {
return nil, err
}
return us, nil
}
func (d *dashboardStore) GetDashboards(ctx context.Context, query *dashboards.GetDashboardsQuery) ([]*dashboards.Dashboard, error) {
ctx, span := tracer.Start(ctx, "dashboards.database.GetDashboards")
defer span.End()
var dashboards = make([]*dashboards.Dashboard, 0)
err := d.store.WithDbSession(ctx, func(sess *db.Session) error {
if len(query.DashboardIDs) == 0 && len(query.DashboardUIDs) == 0 {
return star.ErrCommandValidationFailed
}
// remove soft deleted dashboards from the response
sess.Where("deleted IS NULL")
if len(query.DashboardIDs) > 0 {
sess.In("id", query.DashboardIDs)
} else {
sess.In("uid", query.DashboardUIDs)
}
if query.OrgID > 0 {
sess.Where("org_id = ?", query.OrgID)
}
err := sess.Find(&dashboards)
return err
})
if err != nil {
return nil, err
}
return dashboards, nil
}
func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) {
ctx, span := tracer.Start(ctx, "dashboards.database.FindDashboards")
defer span.End()
@ -1025,39 +932,6 @@ func (d *dashboardStore) GetDashboardTags(ctx context.Context, query *dashboards
return queryResult, nil
}
// CountDashboardsInFolder returns a count of all dashboards associated with the
// given parent folder ID.
func (d *dashboardStore) CountDashboardsInFolders(
ctx context.Context, req *dashboards.CountDashboardsInFolderRequest) (int64, error) {
ctx, span := tracer.Start(ctx, "dashboards.database.CountDashboardsInFolders")
defer span.End()
if len(req.FolderUIDs) == 0 {
return 0, nil
}
var count int64
err := d.store.WithDbSession(ctx, func(sess *db.Session) error {
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc()
s := strings.Builder{}
args := make([]any, 0, 3)
s.WriteString("SELECT COUNT(*) FROM dashboard WHERE ")
if len(req.FolderUIDs) == 1 && req.FolderUIDs[0] == "" {
s.WriteString("folder_uid IS NULL")
} else {
s.WriteString(fmt.Sprintf("folder_uid IN (%s)", strings.Repeat("?,", len(req.FolderUIDs)-1)+"?"))
for _, folderUID := range req.FolderUIDs {
args = append(args, folderUID)
}
}
s.WriteString(" AND org_id = ? AND is_folder = ? AND deleted IS NULL")
args = append(args, req.OrgID, d.store.GetDialect().BooleanValue(false))
sql := s.String()
_, err := sess.SQL(sql, args...).Get(&count)
return err
})
return count, err
}
func (d *dashboardStore) DeleteDashboardsInFolders(
ctx context.Context, req *dashboards.DeleteDashboardsInFolderRequest) error {
ctx, span := tracer.Start(ctx, "dashboards.database.DeleteDashboardsInFolders")

View File

@ -59,42 +59,6 @@ func TestIntegrationDashboardProvisioningTest(t *testing.T) {
require.NotEqual(t, 0, dash.ID)
dashId := dash.ID
t.Run("Deleting orphaned provisioned dashboards", func(t *testing.T) {
saveCmd := dashboards.SaveDashboardCommand{
OrgID: 1,
IsFolder: false,
FolderUID: dash.UID,
Dashboard: simplejson.NewFromAny(map[string]any{
"id": nil,
"title": "another_dashboard",
}),
}
provisioning := &dashboards.DashboardProvisioning{
Name: "another_reader",
ExternalID: "/var/grafana.json",
Updated: now.Unix(),
}
anotherDash, err := dashboardStore.SaveProvisionedDashboard(context.Background(), saveCmd, provisioning)
require.Nil(t, err)
query := &dashboards.GetDashboardsQuery{DashboardIDs: []int64{anotherDash.ID}}
queryResult, err := dashboardStore.GetDashboards(context.Background(), query)
require.Nil(t, err)
require.NotNil(t, queryResult)
deleteCmd := &dashboards.DeleteOrphanedProvisionedDashboardsCommand{ReaderNames: []string{"default"}}
require.Nil(t, dashboardStore.DeleteOrphanedProvisionedDashboards(context.Background(), deleteCmd))
query = &dashboards.GetDashboardsQuery{DashboardIDs: []int64{dash.ID, anotherDash.ID}}
queryResult, err = dashboardStore.GetDashboards(context.Background(), query)
require.Nil(t, err)
require.Equal(t, 1, len(queryResult))
require.Equal(t, dashId, queryResult[0].ID)
})
t.Run("Can query for provisioned dashboards", func(t *testing.T) {
rslt, err := dashboardStore.GetProvisionedDashboardData(context.Background(), "default")
require.Nil(t, err)

View File

@ -10,27 +10,19 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
libmodel "github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/grafana/grafana/pkg/util"
)
@ -185,15 +177,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
require.False(t, queryResult.IsFolder)
})
t.Run("Should be able to get a dashboard UID by ID", func(t *testing.T) {
setup()
query := dashboards.GetDashboardRefByIDQuery{ID: savedDash.ID}
queryResult, err := dashboardStore.GetDashboardUIDByID(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, queryResult.UID, savedDash.UID)
require.Equal(t, queryResult.FolderUID, savedFolder.UID)
})
t.Run("Shouldn't be able to get a dashboard with just an OrgID", func(t *testing.T) {
setup()
query := dashboards.GetDashboardQuery{
@ -216,19 +199,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
require.Error(t, err, dashboards.ErrDashboardNotFound)
})
t.Run("Should be able to get dashboards by IDs & UIDs", func(t *testing.T) {
setup()
query := dashboards.GetDashboardsQuery{DashboardIDs: []int64{savedDash.ID, savedDash2.ID}}
queryResult, err := dashboardStore.GetDashboards(context.Background(), &query)
require.NoError(t, err)
assert.Equal(t, len(queryResult), 2)
query = dashboards.GetDashboardsQuery{DashboardUIDs: []string{savedDash.UID, savedDash2.UID}}
queryResult, err = dashboardStore.GetDashboards(context.Background(), &query)
require.NoError(t, err)
assert.Equal(t, len(queryResult), 2)
})
t.Run("Should be able to delete dashboard and associated tags", func(t *testing.T) {
setup()
dash := insertTestDashboard(t, dashboardStore, "delete me", 1, 0, "", false, "delete this")
@ -330,32 +300,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
require.Len(t, res, 0)
})
t.Run("Should be able to delete all dashboards for an org", func(t *testing.T) {
setup()
dash1 := insertTestDashboard(t, dashboardStore, "delete me", 1, 0, "", false, "delete this")
dash2 := insertTestDashboard(t, dashboardStore, "delete me2", 1, 0, "", false, "delete this2")
dash3 := insertTestDashboard(t, dashboardStore, "dont delete me", 2, 0, "", false, "dont delete me")
err := dashboardStore.DeleteAllDashboards(context.Background(), 1)
require.NoError(t, err)
// no dashboards should exist for org 1
queryResult, err := dashboardStore.GetDashboards(context.Background(), &dashboards.GetDashboardsQuery{
OrgID: 1,
DashboardUIDs: []string{dash1.UID, dash2.UID},
})
require.NoError(t, err)
assert.Equal(t, len(queryResult), 0)
// but we should still have one for org 2
queryResult, err = dashboardStore.GetDashboards(context.Background(), &dashboards.GetDashboardsQuery{
OrgID: 2,
DashboardUIDs: []string{dash3.UID},
})
require.NoError(t, err)
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")
@ -679,22 +623,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
require.Equal(t, len(hit2.Tags), 1)
})
t.Run("Can count dashboards by parent folder", func(t *testing.T) {
setup()
// setup() saves one dashboard in the general folder and two in the "savedFolder".
count, err := dashboardStore.CountDashboardsInFolders(
context.Background(),
&dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{""}, OrgID: 1})
require.NoError(t, err)
require.Equal(t, int64(1), count)
count, err = dashboardStore.CountDashboardsInFolders(
context.Background(),
&dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{savedFolder.UID}, OrgID: 1})
require.NoError(t, err)
require.Equal(t, int64(2), count)
})
t.Run("Can delete dashboards in folder", func(t *testing.T) {
setup()
folder := insertTestDashboard(t, dashboardStore, "dash folder", 1, 0, "", true, "prod", "webapp")
@ -703,10 +631,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
err := dashboardStore.DeleteDashboardsInFolders(context.Background(), &dashboards.DeleteDashboardsInFolderRequest{OrgID: folder.OrgID, FolderUIDs: []string{folder.UID}})
require.NoError(t, err)
count, err := dashboardStore.CountDashboardsInFolders(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{folder.UID}, OrgID: 1})
require.NoError(t, err)
require.Equal(t, count, int64(0))
})
}
@ -830,6 +754,59 @@ func TestIntegrationDashboard_Filter(t *testing.T) {
assert.Equal(t, dashB.ID, results[0].ID)
}
// has to be added to both the folder & the dashboard table
func insertTestFolder(t *testing.T, dashboardStore dashboards.Store, sqlStore db.DB, title string, orgId int64, parentUID string, tags ...interface{}) *dashboards.Dashboard {
t.Helper()
cmd := dashboards.SaveDashboardCommand{
OrgID: orgId,
FolderUID: parentUID,
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
dash, err := dashboardStore.SaveDashboard(context.Background(), cmd)
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.ID)
dash.Data.Set("uid", dash.UID)
err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error {
type folder struct {
ID int64 `xorm:"pk autoincr 'id'"`
OrgID int64 `xorm:"org_id"`
UID string `xorm:"uid"`
ParentUID *string `xorm:"parent_uid"`
Title string `xorm:"title"`
Description string `xorm:"description"`
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
f := &folder{
OrgID: orgId,
UID: dash.UID,
Title: title,
Description: "",
Created: time.Now(),
Updated: time.Now(),
}
if parentUID != "" {
f.ParentUID = &parentUID
}
_, err := sess.Insert(f)
return err
})
require.NoError(t, err)
return dash
}
func TestIntegrationFindDashboardsByTitle(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
@ -844,13 +821,6 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
orgID := int64(1)
insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false, []string{"tag1", "tag2"})
ac := acimpl.ProvideAccessControl(features)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fStore := folderimpl.ProvideStore(sqlStore)
folderServiceWithFlagOn := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
user := &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
@ -863,23 +833,10 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
},
}
f0, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "f0",
SignedInUser: user,
})
require.NoError(t, err)
f0 := insertTestFolder(t, dashboardStore, sqlStore, "f0", orgID, "")
insertTestDashboard(t, dashboardStore, "dashboard under f0", orgID, 0, f0.UID, false, []string{"tag3"})
subfolder, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "subfolder",
ParentUID: f0.UID,
SignedInUser: user,
})
require.NoError(t, err)
subfolder := insertTestFolder(t, dashboardStore, sqlStore, "subfolder", orgID, f0.UID)
insertTestDashboard(t, dashboardStore, "dashboard under subfolder", orgID, 0, subfolder.UID, false)
type res struct {
@ -981,14 +938,6 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
orgID := int64(1)
insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false)
ac := acimpl.ProvideAccessControl(features)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fStore := folderimpl.ProvideStore(sqlStore)
folderServiceWithFlagOn := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
user := &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
@ -1001,31 +950,13 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
},
}
f0, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "f0",
SignedInUser: user,
})
require.NoError(t, err)
// nolint:staticcheck
insertTestDashboard(t, dashboardStore, "dashboard under f0", orgID, f0.ID, f0.UID, false)
f0 := insertTestFolder(t, dashboardStore, sqlStore, "f0", orgID, "")
insertTestDashboard(t, dashboardStore, "dashboard under f0", orgID, 0, f0.UID, false)
f1, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "f1",
SignedInUser: user,
})
require.NoError(t, err)
// nolint:staticcheck
insertTestDashboard(t, dashboardStore, "dashboard under f1", orgID, f1.ID, f1.UID, false)
f1 := insertTestFolder(t, dashboardStore, sqlStore, "f1", orgID, "")
insertTestDashboard(t, dashboardStore, "dashboard under f1", orgID, 0, f1.UID, false)
subfolder, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: orgID,
Title: "subfolder",
ParentUID: f0.UID,
SignedInUser: user,
})
require.NoError(t, err)
subfolder := insertTestFolder(t, dashboardStore, sqlStore, "subfolder", orgID, f0.UID)
type res struct {
title string

View File

@ -150,10 +150,6 @@ func (dr *DashboardServiceImpl) cleanupK8sDashboardResources(ctx context.Context
ctx, span := tracer.Start(ctx, "dashboards.service.cleanupK8sDashboardResources")
defer span.End()
if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return nil
}
readingFromLegacy := dualwrite.IsReadingLegacyDashboardsAndFolders(ctx, dr.dual)
if readingFromLegacy {
// Legacy does its own cleanup
@ -444,7 +440,6 @@ func (dr *DashboardServiceImpl) getPermissionsService(isFolder bool) accesscontr
}
func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
u := &quota.Map{}
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
@ -478,11 +473,8 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc
return u, nil
}
return dr.dashboardStore.Count(ctx, scopeParams)
}
func (dr *DashboardServiceImpl) GetDashboardsByLibraryPanelUID(ctx context.Context, libraryPanelUID string, orgID int64) ([]*dashboards.DashboardRef, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) && dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesLibraryPanelConnections) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesLibraryPanelConnections) {
res, err := dr.k8sclient.Search(ctx, orgID, &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Fields: []*resourcepb.Requirement{
@ -519,7 +511,6 @@ func (dr *DashboardServiceImpl) GetDashboardsByLibraryPanelUID(ctx context.Conte
}
func (dr *DashboardServiceImpl) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
resp, err := dr.k8sclient.GetStats(ctx, orgID)
if err != nil {
return 0, err
@ -532,9 +523,6 @@ func (dr *DashboardServiceImpl) CountDashboardsInOrg(ctx context.Context, orgID
return resp.Stats[0].Count, nil
}
return dr.dashboardStore.CountInOrg(ctx, orgID, false)
}
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
limits := &quota.Map{}
@ -557,7 +545,6 @@ func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, name string) ([]*dashboards.DashboardProvisioning, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
return nil, err
@ -582,11 +569,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context,
return results, nil
}
return dr.dashboardStore.GetProvisionedDashboardData(ctx, name)
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*dashboards.DashboardProvisioning, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// if dashboard id is 0, it is a new dashboard
if dashboardID == 0 {
return nil, nil
@ -617,25 +600,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx con
return nil, nil
}
data, err := dr.dashboardStore.GetProvisionedDataByDashboardID(ctx, dashboardID)
if err != nil {
return nil, err
}
if data == nil {
return nil, nil
}
return &dashboards.DashboardProvisioning{
DashboardID: data.Dashboard.ID,
Name: data.Provisioner,
ExternalID: data.ExternalID,
CheckSum: data.CheckSum,
Updated: data.ProvisionUpdate,
}, nil
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioning, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
if dashboardUID == "" {
return nil, nil
}
@ -658,23 +623,6 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx co
return nil, nil
}
data, err := dr.dashboardStore.GetProvisionedDataByDashboardUID(ctx, orgID, dashboardUID)
if err != nil {
return nil, err
}
if data == nil {
return nil, nil
}
return &dashboards.DashboardProvisioning{
DashboardID: data.Dashboard.ID,
Name: data.Provisioner,
ExternalID: data.ExternalID,
CheckSum: data.CheckSum,
Updated: data.ProvisionUpdate,
}, nil
}
func (dr *DashboardServiceImpl) ValidateBasicDashboardProperties(title string, uid string, message string) error {
if title == "" {
return dashboards.ErrDashboardTitleEmpty
@ -730,7 +678,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
}
// Validate folder
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) && (dash.FolderID != 0 || dash.FolderUID != "") { // nolint:staticcheck
if dash.FolderID != 0 || dash.FolderUID != "" { // nolint:staticcheck
folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: dash.OrgID,
UID: &dash.FolderUID,
@ -744,22 +692,6 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
// nolint:staticcheck
dash.FolderID = folder.ID
dash.FolderUID = folder.UID
} else if dash.FolderUID != "" {
folder, err := dr.folderStore.GetFolderByUID(ctx, dash.OrgID, dash.FolderUID)
if err != nil {
return nil, err
}
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc()
// nolint:staticcheck
dash.FolderID = folder.ID
} else if dash.FolderID != 0 { // nolint:staticcheck
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc()
// nolint:staticcheck
folder, err := dr.folderStore.GetFolderByID(ctx, dash.OrgID, dash.FolderID)
if err != nil {
return nil, err
}
dash.FolderUID = folder.UID
}
isParentFolderChanged, err := dr.ValidateDashboardBeforeSave(ctx, dash, dto.Overwrite)
@ -944,7 +876,6 @@ func (dr *DashboardServiceImpl) waitForSearchQuery(ctx context.Context, query *d
}
func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *dashboards.DeleteOrphanedProvisionedDashboardsCommand) error {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// check each org for orphaned provisioned dashboards
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
@ -983,9 +914,6 @@ func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.
return nil
}
return dr.dashboardStore.DeleteOrphanedProvisionedDashboards(ctx, cmd)
}
func (dr *DashboardServiceImpl) ValidateDashboardRefreshInterval(minRefreshInterval string, targetRefreshInterval string) error {
if minRefreshInterval == "" {
return nil
@ -1034,13 +962,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
return nil, fmt.Errorf("failed to build save dashboard command. cmd is nil")
}
var dash *dashboards.Dashboard
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dash, err = dr.saveProvisionedDashboardThroughK8s(ctx, cmd, provisioning, false)
} else {
dash, err = dr.dashboardStore.SaveProvisionedDashboard(ctx, *cmd, provisioning)
}
dash, err := dr.saveProvisionedDashboardThroughK8s(ctx, cmd, provisioning, false)
if err != nil {
return nil, err
}
@ -1065,10 +987,6 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C
return nil, err
}
// Only set default permissions if the Folder API Server is disabled.
if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dr.setDefaultFolderPermissions(ctx, dto, f)
}
return f, nil
}
@ -1103,13 +1021,9 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar
}
func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboards.SaveDashboardCommand) (*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.saveDashboardThroughK8s(ctx, cmd, cmd.OrgID)
}
return dr.dashboardStore.SaveDashboard(ctx, *cmd)
}
// DeleteDashboard removes dashboard from the DB. Errors out if the dashboard was provisioned. Should be used for
// operations by the user where we want to make sure user does not delete provisioned dashboard.
func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64) error {
@ -1118,13 +1032,9 @@ func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId
// DeleteAllDashboards will delete all dashboards within a given org.
func (dr *DashboardServiceImpl) DeleteAllDashboards(ctx context.Context, orgId int64) error {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.deleteAllDashboardThroughK8s(ctx, orgId)
}
return dr.dashboardStore.DeleteAllDashboards(ctx, orgId)
}
func (dr *DashboardServiceImpl) GetDashboardByPublicUid(ctx context.Context, dashboardPublicUid string) (*dashboards.Dashboard, error) {
return nil, nil
}
@ -1141,30 +1051,9 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId
cmd := &dashboards.DeleteDashboardCommand{OrgID: orgId, ID: dashboardId, UID: dashboardUID}
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.deleteDashboardThroughK8s(ctx, cmd, validateProvisionedDashboard)
}
if validateProvisionedDashboard {
provisionedData, err := dr.GetProvisionedDashboardDataByDashboardID(ctx, dashboardId)
if err != nil {
return fmt.Errorf("%v: %w", "failed to check if dashboard is provisioned", err)
}
if provisionedData != nil {
return dashboards.ErrDashboardCannotDeleteProvisionedDashboard
}
}
// deletes all related public dashboard entities
err := dr.publicDashboardService.DeleteByDashboardUIDs(ctx, orgId, []string{dashboardUID})
if err != nil {
return err
}
return dr.dashboardStore.DeleteDashboard(ctx, cmd)
}
func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO) (
*dashboards.Dashboard, error) {
ctx, span := tracer.Start(ctx, "dashboards.service.ImportDashboard")
@ -1195,7 +1084,6 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo
// UnprovisionDashboard removes info about dashboard being provisioned. Used after provisioning configs are changed
// and provisioned dashboards are left behind but not deleted.
func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashboardId int64) error {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
return err
@ -1227,11 +1115,7 @@ func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashbo
return dashboards.ErrDashboardNotFound
}
return dr.dashboardStore.UnprovisionDashboard(ctx, dashboardId)
}
func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, query *dashboards.GetDashboardsByPluginIDQuery) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
OrgId: query.OrgID,
ManagedBy: utils.ManagerKindPlugin,
@ -1253,8 +1137,6 @@ func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, que
return results, nil
}
return dr.dashboardStore.GetDashboardsByPluginID(ctx, query)
}
// (sometimes) called by the k8s storage engine after creating an object
func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Context, key *resourcepb.ResourceKey, id claims.AuthInfo, obj utils.GrafanaMetaAccessor) error {
@ -1361,34 +1243,11 @@ func (dr *DashboardServiceImpl) SetDefaultPermissions(ctx context.Context, dto *
}
}
func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) || !dr.cfg.RBAC.PermissionsOnCreation("folder") || f.ParentUID != "" {
return
}
ctx, span := tracer.Start(ctx, "dashboards.service.setDefaultFolderPermissions")
defer span.End()
permissions := []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
}
if _, err := dr.folderPermissions.SetPermissions(ctx, cmd.OrgID, f.UID, permissions...); err != nil {
dr.log.Error("Could not set default folder permissions", "folder", f.Title, "error", err)
}
}
func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.getDashboardThroughK8s(ctx, query)
}
return dr.dashboardStore.GetDashboard(ctx, query)
}
func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *dashboards.GetDashboardRefByIDQuery) (*dashboards.DashboardRef, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
requester, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
@ -1410,12 +1269,8 @@ func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *
return &dashboards.DashboardRef{UID: result[0].UID, Slug: result[0].Slug, FolderUID: result[0].FolderUID}, nil
}
return dr.dashboardStore.GetDashboardUIDByID(ctx, query)
}
// expensive query in new flow !! use sparingly - only if you truly need dashboard.Data
func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *dashboards.GetDashboardsQuery) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
if query.OrgID == 0 {
requester, err := identity.GetRequester(ctx)
if err != nil {
@ -1446,9 +1301,6 @@ func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *dashbo
return results, nil
}
return dr.dashboardStore.GetDashboards(ctx, query)
}
func (dr *DashboardServiceImpl) getDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*dashboards.DashboardRef, error) {
ctx, span := tracer.Start(ctx, "dashboards.service.getDashboardsSharedWithUser")
defer span.End()
@ -1468,21 +1320,10 @@ func (dr *DashboardServiceImpl) getDashboardsSharedWithUser(ctx context.Context,
return []*dashboards.DashboardRef{}, nil
}
dashboardsQuery := &dashboards.GetDashboardsQuery{
DashboardUIDs: dashboardUids,
OrgID: user.GetOrgID(),
}
var err error
var dashs []*dashboards.Dashboard
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashs, err = dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
DashboardUIDs: dashboardUids,
OrgId: user.GetOrgID(),
})
} else {
dashs, err = dr.dashboardStore.GetDashboards(ctx, dashboardsQuery)
}
if err != nil {
return nil, err
}
@ -1573,7 +1414,6 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
}(time.Now())
}
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
if query.OrgId == 0 {
requester, err := identity.GetRequester(ctx)
if err != nil {
@ -1633,9 +1473,6 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
return finalResults, nil
}
return dr.dashboardStore.FindDashboards(ctx, query)
}
type folderRes struct {
Title string
ID int64
@ -1680,13 +1517,9 @@ func (dr *DashboardServiceImpl) SearchDashboards(ctx context.Context, query *das
}
func (dr *DashboardServiceImpl) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
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 {
@ -1743,7 +1576,6 @@ func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashb
}
func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) {
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
res, err := dr.k8sclient.Search(ctx, query.OrgID, &resourcepb.ResourceSearchRequest{
Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{
"tags": {
@ -1771,11 +1603,7 @@ func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *das
return results, nil
}
return dr.dashboardStore.GetDashboardTags(ctx, query)
}
func (dr DashboardServiceImpl) CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, u identity.Requester) (int64, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
OrgId: orgID,
FolderUIDs: folderUIDs,
@ -1787,9 +1615,6 @@ func (dr DashboardServiceImpl) CountInFolders(ctx context.Context, orgID int64,
return int64(len(dashs)), nil
}
return dr.dashboardStore.CountDashboardsInFolders(ctx, &dashboards.CountDashboardsInFolderRequest{FolderUIDs: folderUIDs, OrgID: orgID})
}
func (dr *DashboardServiceImpl) DeleteInFolders(ctx context.Context, orgID int64, folderUIDs []string, u identity.Requester) error {
ctx, span := tracer.Start(ctx, "dashboards.service.DeleteInFolders")
defer span.End()

View File

@ -54,25 +54,24 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestDashboardService(t *testing.T) {
t.Run("Dashboard service tests", func(t *testing.T) {
func TestDashboardServiceValidation(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
fakePublicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
defer fakeStore.AssertExpectations(t)
folderSvc := foldertest.NewFakeService()
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
log: log.New("test.logger"),
dashboardStore: &fakeStore,
folderService: folderSvc,
folderService: foldertest.NewFakeService(),
ac: actest.FakeAccessControl{ExpectedEvaluate: true},
features: featuremgmt.WithFeatures(),
publicDashboardService: fakePublicDashboardService,
}
folderStore := foldertest.FakeFolderStore{}
folderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(nil, dashboards.ErrFolderNotFound).Once()
service.folderStore = &folderStore
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil)
t.Run("Save dashboard validation", func(t *testing.T) {
dto := &dashboards.SaveDashboardDTO{}
@ -82,7 +81,7 @@ func TestDashboardService(t *testing.T) {
for _, title := range titles {
dto.Dashboard = dashboards.NewDashboard(title)
_, err := service.SaveDashboard(context.Background(), dto, false)
_, err := service.SaveDashboard(ctx, dto, false)
require.Equal(t, err, dashboards.ErrDashboardTitleEmpty)
}
})
@ -95,8 +94,8 @@ func TestDashboardService(t *testing.T) {
will share this with you, as a form of payment for having to read this:
https://youtu.be/dQw4w9WgXcQ?si=KeoTIpn9tUtQnOBk! Enjoy :) Now lets see if
this test passes or if the result is more exciting than these 500 characters
I wrote. Best of luck to the both of us!`
_, err := service.SaveDashboard(context.Background(), dto, false)
I wrote. Best of luck to the both of us!!`
_, err := service.SaveDashboard(ctx, dto, false)
require.Equal(t, err, dashboards.ErrDashboardMessageTooLong)
// set to a shorter message for the rest of the tests
@ -105,7 +104,7 @@ func TestDashboardService(t *testing.T) {
t.Run("Should return validation error if folder is named General", func(t *testing.T) {
dto.Dashboard = dashboards.NewDashboardFolder("General")
_, err := service.SaveDashboard(context.Background(), dto, false)
_, err := service.SaveDashboard(ctx, dto, false)
require.Equal(t, err, dashboards.ErrDashboardFolderNameExists)
})
@ -129,9 +128,9 @@ func TestDashboardService(t *testing.T) {
dto.User = &user.SignedInUser{}
if tc.Error == nil {
fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once()
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Once()
}
_, err := service.BuildSaveDashboardCommand(context.Background(), dto, false)
_, err := service.BuildSaveDashboardCommand(ctx, dto, false)
require.Equal(t, err, tc.Error)
}
})
@ -141,137 +140,15 @@ func TestDashboardService(t *testing.T) {
dto.Dashboard.FolderUID = "non-existing-folder"
folderSvc := foldertest.FakeService{ExpectedError: dashboards.ErrFolderNotFound}
service.folderService = &folderSvc
_, err := service.SaveDashboard(context.Background(), dto, false)
_, err := service.SaveDashboard(ctx, dto, false)
require.Equal(t, err, dashboards.ErrFolderNotFound)
})
t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) {
fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once()
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once()
dto.Dashboard = dashboards.NewDashboard("Dash")
dto.Dashboard.SetID(3)
dto.User = &user.SignedInUser{UserID: 1}
_, err := service.SaveDashboard(context.Background(), dto, false)
require.Equal(t, err, dashboards.ErrDashboardCannotSaveProvisionedDashboard)
})
t.Run("Should not return validation error if dashboard is provisioned but UI updates allowed", func(t *testing.T) {
fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once()
fakeStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once()
dto.Dashboard = dashboards.NewDashboard("Dash")
dto.Dashboard.SetID(3)
dto.User = &user.SignedInUser{UserID: 1}
_, err := service.SaveDashboard(context.Background(), dto, true)
require.NoError(t, err)
})
})
t.Run("Save provisioned dashboard validation", func(t *testing.T) {
dto := &dashboards.SaveDashboardDTO{}
t.Run("Should not return validation error if dashboard is provisioned", func(t *testing.T) {
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand"), mock.AnythingOfType("*dashboards.DashboardProvisioning")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once()
dto.Dashboard = dashboards.NewDashboard("Dash")
dto.Dashboard.SetID(3)
dto.User = &user.SignedInUser{UserID: 1}
_, err := service.SaveProvisionedDashboard(context.Background(), dto, nil)
require.NoError(t, err)
})
t.Run("Should override invalid refresh interval if dashboard is provisioned", func(t *testing.T) {
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand"), mock.AnythingOfType("*dashboards.DashboardProvisioning")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once()
oldRefreshInterval := service.cfg.MinRefreshInterval
service.cfg.MinRefreshInterval = "5m"
defer func() { service.cfg.MinRefreshInterval = oldRefreshInterval }()
dto.Dashboard = dashboards.NewDashboard("Dash")
dto.Dashboard.SetID(3)
dto.User = &user.SignedInUser{UserID: 1}
dto.Dashboard.Data.Set("refresh", "1s")
_, err := service.SaveProvisionedDashboard(context.Background(), dto, nil)
require.NoError(t, err)
require.Equal(t, dto.Dashboard.Data.Get("refresh").MustString(), "5m")
})
})
t.Run("Import dashboard validation", func(t *testing.T) {
dto := &dashboards.SaveDashboardDTO{}
t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) {
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once()
dto.Dashboard = dashboards.NewDashboard("Dash")
dto.Dashboard.SetID(3)
dto.User = &user.SignedInUser{UserID: 1}
_, err := service.ImportDashboard(context.Background(), dto)
require.Equal(t, err, dashboards.ErrDashboardCannotSaveProvisionedDashboard)
})
})
t.Run("Given provisioned dashboard", func(t *testing.T) {
t.Run("DeleteProvisionedDashboard should delete it", func(t *testing.T) {
args := &dashboards.DeleteDashboardCommand{OrgID: 1, ID: 1}
fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once()
fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteProvisionedDashboard(context.Background(), 1, 1)
require.NoError(t, err)
})
t.Run("DeleteDashboard should fail to delete it when provisioning information is missing", func(t *testing.T) {
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once()
err := service.DeleteDashboard(context.Background(), 1, "", 1)
require.Equal(t, err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard)
})
})
t.Run("Given non provisioned dashboard", func(t *testing.T) {
t.Run("DeleteProvisionedDashboard should delete the dashboard", func(t *testing.T) {
args := &dashboards.DeleteDashboardCommand{OrgID: 1, ID: 1}
fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once()
fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteProvisionedDashboard(context.Background(), 1, 1)
require.NoError(t, err)
})
t.Run("DeleteDashboard should delete it", func(t *testing.T) {
args := &dashboards.DeleteDashboardCommand{OrgID: 1, ID: 1}
fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once()
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(nil, nil).Once()
fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteDashboard(context.Background(), 1, "", 1)
require.NoError(t, err)
})
})
t.Run("Count dashboards in folder", func(t *testing.T) {
fakeStore.On("CountDashboardsInFolders", mock.Anything, mock.AnythingOfType("*dashboards.CountDashboardsInFolderRequest")).Return(int64(3), nil)
folderSvc.ExpectedFolder = &folder.Folder{UID: "i am a folder"}
// set up a ctx with signed in user
usr := &user.SignedInUser{UserID: 1}
ctx := identity.WithRequester(context.Background(), usr)
count, err := service.CountInFolders(ctx, 1, []string{"i am a folder"}, usr)
require.NoError(t, err)
require.Equal(t, int64(3), count)
})
t.Run("Delete dashboards in folder", func(t *testing.T) {
args := &dashboards.DeleteDashboardsInFolderRequest{OrgID: 1, FolderUIDs: []string{"uid"}}
fakeStore.On("DeleteDashboardsInFolders", mock.Anything, args).Return(nil).Once()
fakeStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteInFolders(context.Background(), 1, []string{"uid"}, nil)
require.NoError(t, err)
})
})
}
func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *client.MockK8sHandler) {
mockCli := new(client.MockK8sHandler)
service.k8sclient = mockCli
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
ctx := context.Background()
userCtx := &user.SignedInUser{UserID: 1, OrgID: 1}
@ -281,27 +158,15 @@ func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *cl
}
func TestGetDashboard(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
query := &dashboards.GetDashboardQuery{
UID: "test-uid",
OrgID: 1,
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetDashboard", mock.Anything, query).Return(&dashboards.Dashboard{}, nil).Once()
dashboard, err := service.GetDashboard(context.Background(), query)
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) {
t.Run("Should get dashboard", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
"metadata": map[string]any{
@ -421,23 +286,9 @@ func TestGetDashboard(t *testing.T) {
}
func TestGetAllDashboards(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, int64(1)).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, k8sCliMock := setupK8sDashboardTests(service)
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
@ -469,27 +320,12 @@ func TestGetAllDashboards(t *testing.T) {
k8sCliMock.AssertExpectations(t)
// make sure the conversion is working
require.True(t, reflect.DeepEqual(dashes, []*dashboards.Dashboard{&dashboardExpected}))
})
}
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, int64(1)).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, k8sCliMock := setupK8sDashboardTests(service)
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
@ -521,30 +357,16 @@ func TestGetAllDashboardsByOrgId(t *testing.T) {
k8sCliMock.AssertExpectations(t)
// make sure the conversion is working
require.True(t, reflect.DeepEqual(dashes, []*dashboards.Dashboard{&dashboardExpected}))
})
}
func TestGetProvisionedDashboardData(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
orgService: &orgtest.FakeOrgService{
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
},
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetProvisionedDashboardData", mock.Anything, "test").Return([]*dashboards.DashboardProvisioning{}, nil).Once()
dashboard, err := service.GetProvisionedDashboardData(context.Background(), "test")
require.NoError(t, err)
require.NotNil(t, dashboard)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled and get from relevant org", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
provisioningTimestamp := int64(1234567)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
@ -634,30 +456,16 @@ func TestGetProvisionedDashboardData(t *testing.T) {
Updated: provisioningTimestamp,
})
k8sCliMock.AssertExpectations(t)
})
}
func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
orgService: &orgtest.FakeOrgService{
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
},
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, int64(1)).Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once()
dashboard, err := service.GetProvisionedDashboardDataByDashboardID(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 and get from whatever org it is in", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
provisioningTimestamp := int64(1234567)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
@ -736,30 +544,15 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) {
Updated: provisioningTimestamp,
})
k8sCliMock.AssertExpectations(t)
})
}
func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
orgService: &orgtest.FakeOrgService{
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
},
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetProvisionedDataByDashboardUID", mock.Anything, int64(1), "test").Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once()
dashboard, err := service.GetProvisionedDashboardDataByDashboardUID(context.Background(), 1, "test")
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, k8sCliMock := setupK8sDashboardTests(service)
provisioningTimestamp := int64(1234567)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
@ -831,16 +624,12 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
Updated: provisioningTimestamp,
})
k8sCliMock.AssertExpectations(t)
})
}
func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
fakePublicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
orgService: &orgtest.FakeOrgService{
ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}},
},
@ -848,19 +637,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
log: log.NewNopLogger(),
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("DeleteOrphanedProvisionedDashboards", mock.Anything, &dashboards.DeleteOrphanedProvisionedDashboardsCommand{
ReaderNames: []string{"test"},
}).Return(nil).Once()
err := service.DeleteOrphanedProvisionedDashboards(context.Background(), &dashboards.DeleteOrphanedProvisionedDashboardsCommand{
ReaderNames: []string{"test"},
})
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled, delete across all orgs, but only delete file based provisioned dashboards", func(t *testing.T) {
t.Run("Should delete across all orgs, but only delete file based provisioned dashboards", func(t *testing.T) {
_, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
@ -1008,7 +785,6 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
repo := "test"
singleOrgService := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
orgService: &orgtest.FakeOrgService{
ExpectedOrgs: []*org.OrgDTO{{ID: 1}},
},
@ -1102,7 +878,6 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
repo := "test"
singleOrgService := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
orgService: &orgtest.FakeOrgService{
ExpectedOrgs: []*org.OrgDTO{{ID: 1}},
},
@ -1141,15 +916,6 @@ func TestUnprovisionDashboard(t *testing.T) {
},
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("UnprovisionDashboard", mock.Anything, int64(1)).Return(nil).Once()
err := service.UnprovisionDashboard(context.Background(), 1)
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled - should remove annotations", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
dash := &unstructured.Unstructured{Object: map[string]any{
"metadata": map[string]any{
@ -1216,17 +982,12 @@ func TestUnprovisionDashboard(t *testing.T) {
require.NoError(t, err)
k8sCliMock.AssertExpectations(t)
fakeStore.AssertExpectations(t)
})
}
func TestGetDashboardsByPluginID(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
query := &dashboards.GetDashboardsByPluginIDQuery{
PluginID: "testing",
OrgID: 1,
@ -1240,15 +1001,6 @@ func TestGetDashboardsByPluginID(t *testing.T) {
},
}}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetDashboardsByPluginID", mock.Anything, mock.Anything).Return([]*dashboards.Dashboard{}, nil).Once()
_, err := service.GetDashboardsByPluginID(context.Background(), query)
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(uidUnstructured, nil)
@ -1288,33 +1040,12 @@ func TestGetDashboardsByPluginID(t *testing.T) {
require.NoError(t, err)
require.Len(t, dashes, 1)
k8sCliMock.AssertExpectations(t)
})
}
func TestSetDefaultPermissionsWhenSavingFolderForProvisionedDashboards(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
type testCase struct {
description string
expectedCallsToSetPermissions int
featuresArr []any
}
tcs := []testCase{
{
description: "folder creation succeeds, via legacy storage",
expectedCallsToSetPermissions: 1,
},
{
description: "folder creation succeeds, via API Server",
expectedCallsToSetPermissions: 0,
featuresArr: []any{featuremgmt.FlagKubernetesClientDashboardsFolders},
},
}
for _, tc := range tcs {
t.Run(tc.description, func(t *testing.T) {
folderPermService := acmock.NewMockedPermissionsService()
folderPermService.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
@ -1345,14 +1076,12 @@ func TestSetDefaultPermissionsWhenSavingFolderForProvisionedDashboards(t *testin
OrgID: 1,
}
service.features = featuremgmt.WithFeatures(tc.featuresArr...)
service.features = featuremgmt.WithFeatures()
folder, err := service.SaveFolderForProvisionedDashboards(context.Background(), cmd)
require.NoError(t, err)
require.NotNil(t, folder)
folderPermService.AssertNumberOfCalls(t, "SetPermissions", tc.expectedCallsToSetPermissions)
})
}
folderPermService.AssertNumberOfCalls(t, "SetPermissions", 0)
}
func TestSaveProvisionedDashboard(t *testing.T) {
@ -1382,17 +1111,6 @@ func TestSaveProvisionedDashboard(t *testing.T) {
Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid"}),
},
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
dashboard, err := service.SaveProvisionedDashboard(context.Background(), query, &dashboards.DashboardProvisioning{})
require.NoError(t, err)
require.NotNil(t, dashboard)
fakeStore.AssertExpectations(t)
})
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
"metadata": map[string]any{
"name": "uid",
@ -1404,9 +1122,8 @@ func TestSaveProvisionedDashboard(t *testing.T) {
},
}}
t.Run("Should use Kubernetes create if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil)
k8sCliMock.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil)
k8sCliMock.On("Update", mock.Anything, mock.Anything, mock.Anything, metav1.UpdateOptions{
FieldValidation: metav1.FieldValidationIgnore,
@ -1419,7 +1136,6 @@ func TestSaveProvisionedDashboard(t *testing.T) {
k8sCliMock.AssertExpectations(t)
// ensure the provisioning data is still saved to the db
fakeStore.AssertExpectations(t)
})
}
func TestSaveDashboard(t *testing.T) {
@ -1446,17 +1162,6 @@ func TestSaveDashboard(t *testing.T) {
},
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.Anything).Return(nil, nil)
fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
fakeStore.On("SaveDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
dashboard, err := service.SaveDashboard(context.Background(), query, false)
require.NoError(t, err)
require.NotNil(t, dashboard)
fakeStore.AssertExpectations(t)
})
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
"metadata": map[string]any{
"name": "uid",
@ -1470,6 +1175,7 @@ func TestSaveDashboard(t *testing.T) {
t.Run("Should use Kubernetes create if feature flags are enabled and dashboard doesn't exist", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
k8sCliMock.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil)
k8sCliMock.On("GetNamespace", mock.Anything).Return("default")
k8sCliMock.On("Update", mock.Anything, mock.Anything, mock.Anything, metav1.UpdateOptions{
@ -1483,6 +1189,7 @@ func TestSaveDashboard(t *testing.T) {
t.Run("Should use Kubernetes update if feature flags are enabled and dashboard exists", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil)
k8sCliMock.On("GetUsersFromMeta", mock.Anything, mock.Anything).Return(map[string]*user.User{}, nil)
k8sCliMock.On("GetNamespace", mock.Anything).Return("default")
k8sCliMock.On("Update", mock.Anything, mock.Anything, mock.Anything, metav1.UpdateOptions{
@ -1517,17 +1224,6 @@ func TestDeleteDashboard(t *testing.T) {
publicDashboardService: fakePublicDashboardService,
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("DeleteDashboard", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.Anything).Return(nil, nil).Once()
fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteDashboard(context.Background(), 1, "uid", 1)
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
@ -1583,22 +1279,12 @@ func TestDeleteAllDashboards(t *testing.T) {
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("DeleteAllDashboards", mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteAllDashboards(context.Background(), 1)
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
err := service.DeleteAllDashboards(ctx, 1)
require.NoError(t, err)
k8sCliMock.AssertExpectations(t)
})
}
func TestSearchDashboards(t *testing.T) {
@ -1612,6 +1298,7 @@ func TestSearchDashboards(t *testing.T) {
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
dashboardStore: &fakeStore,
folderService: fakeFolders,
metrics: newDashboardsMetrics(prometheus.NewRegistry()),
@ -1651,38 +1338,8 @@ func TestSearchDashboards(t *testing.T) {
query := dashboards.FindPersistedDashboardsQuery{
DashboardUIDs: []string{"uid1", "uid2"},
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
{
UID: "uid1",
Slug: "dashboard-1",
OrgID: 1,
Title: "Dashboard 1",
Tags: []string{"tag1", "tag2"},
FolderTitle: "testing-folder-1",
FolderSlug: "testing-folder-1",
FolderUID: "f1",
FolderID: 1,
},
{
UID: "uid2",
Slug: "dashboard-2",
OrgID: 1,
Title: "Dashboard 2",
FolderTitle: "testing-folder-1",
FolderSlug: "testing-folder-1",
FolderUID: "f1",
FolderID: 1,
},
}, nil).Once()
result, err := service.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, expectedResult, result)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
t.Run("Should search correctly", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
expectedFolders := model.HitList{
{
@ -1745,7 +1402,7 @@ func TestSearchDashboards(t *testing.T) {
t.Run("Should handle Shared with me folder correctly", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
service.features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagKubernetesClientDashboardsFolders)
service.features = featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
if len(req.Options.Fields) == 0 {
@ -1848,11 +1505,8 @@ func TestSearchDashboards(t *testing.T) {
}
func TestGetDashboards(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
expectedResult := []*dashboards.Dashboard{
@ -1899,25 +1553,7 @@ func TestGetDashboards(t *testing.T) {
DashboardUIDs: []string{"uid1", "uid2"},
OrgID: 1,
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
// by ids
fakeStore.On("GetDashboards", mock.Anything, queryByIDs).Return(expectedResult, nil).Once()
result, err := service.GetDashboards(context.Background(), queryByIDs)
require.NoError(t, err)
require.Equal(t, expectedResult, result)
fakeStore.AssertExpectations(t)
// by uids
fakeStore.On("GetDashboards", mock.Anything, queryByUIDs).Return(expectedResult, nil).Once()
result, err = service.GetDashboards(context.Background(), queryByUIDs)
require.NoError(t, err)
require.Equal(t, expectedResult, result)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Get", mock.Anything, "uid1", mock.Anything, mock.Anything, mock.Anything).Return(uid1Unstructured, nil)
@ -1972,15 +1608,11 @@ func TestGetDashboards(t *testing.T) {
require.NoError(t, err)
require.Equal(t, expectedResult, result)
k8sCliMock.AssertExpectations(t)
})
}
func TestGetDashboardUIDByID(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
expectedResult := &dashboards.DashboardRef{
@ -1991,17 +1623,6 @@ func TestGetDashboardUIDByID(t *testing.T) {
query := &dashboards.GetDashboardRefByIDQuery{
ID: 1,
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetDashboardUIDByID", mock.Anything, query).Return(expectedResult, nil).Once()
result, err := service.GetDashboardUIDByID(context.Background(), query)
require.NoError(t, err)
require.Equal(t, expectedResult, result)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
@ -2035,7 +1656,6 @@ func TestGetDashboardUIDByID(t *testing.T) {
require.NoError(t, err)
require.Equal(t, expectedResult, result)
k8sCliMock.AssertExpectations(t)
})
}
func TestUnstructuredToLegacyDashboard(t *testing.T) {
@ -2092,11 +1712,8 @@ func TestUnstructuredToLegacyDashboard(t *testing.T) {
}
func TestGetDashboardTags(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
expectedResult := []*dashboards.DashboardTagCloudItem{
@ -2112,16 +1729,6 @@ func TestGetDashboardTags(t *testing.T) {
query := &dashboards.GetDashboardTagsQuery{
OrgID: 1,
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("GetDashboardTags", mock.Anything, query).Return(expectedResult, nil).Once()
result, err := service.GetDashboardTags(context.Background(), query)
require.NoError(t, err)
require.Equal(t, expectedResult, result)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{
Facet: map[string]*resourcepb.ResourceSearchResponse_Facet{
@ -2143,16 +1750,11 @@ func TestGetDashboardTags(t *testing.T) {
result, err := service.GetDashboardTags(ctx, query)
require.NoError(t, err)
require.Equal(t, expectedResult, result)
fakeStore.AssertExpectations(t)
})
}
func TestQuotaCount(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
orgs := []*org.OrgDTO{
@ -2182,15 +1784,6 @@ func TestQuotaCount(t *testing.T) {
query := &quota.ScopeParameters{
OrgID: 1,
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("Count", mock.Anything, mock.Anything).Return(nil, nil).Once()
_, err := service.Count(context.Background(), query)
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
orgSvc := orgtest.FakeOrgService{ExpectedOrgs: orgs}
service.orgService = &orgSvc
@ -2209,17 +1802,11 @@ func TestQuotaCount(t *testing.T) {
require.NoError(t, err)
c, _ = result.Get(globalTag)
require.Equal(t, c, int64(3))
fakeStore.AssertExpectations(t)
})
}
func TestCountDashboardsInOrg(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
count := resourcepb.ResourceStatsResponse{
Stats: []*resourcepb.ResourceStatsResponse_Stats{
@ -2229,30 +1816,18 @@ func TestCountDashboardsInOrg(t *testing.T) {
},
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("CountInOrg", mock.Anything, mock.Anything, false).Return(int64(1), nil).Once()
_, err := service.CountDashboardsInOrg(context.Background(), 1)
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetStats", mock.Anything, mock.Anything).Return(&count, nil).Once()
result, err := service.CountDashboardsInOrg(ctx, 1)
require.NoError(t, err)
require.Equal(t, result, int64(3))
})
}
func TestCountInFolders(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
}
ctx, k8sCliMock := setupK8sDashboardTests(service)
dashs := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
@ -2291,22 +1866,11 @@ func TestCountInFolders(t *testing.T) {
TotalHits: 2,
}
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
service.features = featuremgmt.WithFeatures()
fakeStore.On("CountDashboardsInFolders", mock.Anything, mock.Anything).Return(int64(1), nil).Once()
_, err := service.CountInFolders(context.Background(), 1, []string{"folder1"}, &user.SignedInUser{})
require.NoError(t, err)
fakeStore.AssertExpectations(t)
})
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
ctx, k8sCliMock := setupK8sDashboardTests(service)
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(dashs, nil).Once()
result, err := service.CountInFolders(ctx, 1, []string{"folder1"}, &user.SignedInUser{})
require.NoError(t, err)
require.Equal(t, result, int64(2))
})
}
func TestSearchDashboardsThroughK8sRaw(t *testing.T) {
@ -2654,26 +2218,18 @@ func TestIntegrationK8sDashboardCleanupJob(t *testing.T) {
}
tests := []struct {
name string
featureEnabled bool
readFromUnified bool
batchSize int
setupFunc func(*DashboardServiceImpl, context.Context, *client.MockK8sHandler)
verifyFunc func(*testing.T, *DashboardServiceImpl, context.Context, *client.MockK8sHandler, *kvstore.FakeKVStore)
}{
{
name: "Should not run cleanup when feature flag is disabled",
featureEnabled: false,
batchSize: 10,
},
{
name: "Should not run cleanup when feature flag is enabled but we're reading from legacy",
featureEnabled: true,
name: "Should not run cleanup when we're reading from legacy",
readFromUnified: false,
batchSize: 10,
},
{
name: "Should process dashboard cleanup for all orgs",
featureEnabled: true,
readFromUnified: true,
batchSize: 10,
setupFunc: func(service *DashboardServiceImpl, ctx context.Context, k8sCliMock *client.MockK8sHandler) {
@ -2744,7 +2300,6 @@ func TestIntegrationK8sDashboardCleanupJob(t *testing.T) {
},
{
name: "Should handle pagination and batching when processing large sets of dashboards",
featureEnabled: true,
readFromUnified: true,
batchSize: 3,
setupFunc: func(service *DashboardServiceImpl, ctx context.Context, k8sCliMock *client.MockK8sHandler) {
@ -2836,11 +2391,7 @@ func TestIntegrationK8sDashboardCleanupJob(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
fakePublicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
fakeOrgService := orgtest.NewOrgServiceFake()
features := featuremgmt.WithFeatures()
if tc.featureEnabled {
features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
}
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
@ -2885,7 +2436,7 @@ func TestIntegrationK8sDashboardCleanupJob(t *testing.T) {
service := &DashboardServiceImpl{
cfg: cfg,
log: log.New("test.logger"),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
features: featuremgmt.WithFeatures(),
serverLockService: lockService,
}
@ -2942,7 +2493,7 @@ func TestGetDashboardsByLibraryPanelUID(t *testing.T) {
dashboardStore: &fakeStore,
folderService: folderSvc,
ac: actest.FakeAccessControl{ExpectedEvaluate: true},
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagKubernetesLibraryPanelConnections),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesLibraryPanelConnections),
publicDashboardService: fakePublicDashboardService,
k8sclient: k8sCliMock,
}

View File

@ -62,34 +62,6 @@ func (_m *FakeDashboardStore) Count(_a0 context.Context, _a1 *quota.ScopeParamet
return r0, r1
}
// CountDashboardsInFolders provides a mock function with given fields: ctx, request
func (_m *FakeDashboardStore) CountDashboardsInFolders(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error) {
ret := _m.Called(ctx, request)
if len(ret) == 0 {
panic("no return value specified for CountDashboardsInFolders")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *CountDashboardsInFolderRequest) (int64, error)); ok {
return rf(ctx, request)
}
if rf, ok := ret.Get(0).(func(context.Context, *CountDashboardsInFolderRequest) int64); ok {
r0 = rf(ctx, request)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(context.Context, *CountDashboardsInFolderRequest) error); ok {
r1 = rf(ctx, request)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CountInOrg provides a mock function with given fields: ctx, orgID, isFolder
func (_m *FakeDashboardStore) CountInOrg(ctx context.Context, orgID int64, isFolder bool) (int64, error) {
ret := _m.Called(ctx, orgID, isFolder)
@ -118,23 +90,6 @@ func (_m *FakeDashboardStore) CountInOrg(ctx context.Context, orgID int64, isFol
return r0, r1
}
// DeleteAllDashboards provides a mock function with given fields: ctx, orgID
func (_m *FakeDashboardStore) DeleteAllDashboards(ctx context.Context, orgID int64) error {
ret := _m.Called(ctx, orgID)
if len(ret) == 0 {
panic("no return value specified for DeleteAllDashboards")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, orgID)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteDashboard provides a mock function with given fields: ctx, cmd
func (_m *FakeDashboardStore) DeleteDashboard(ctx context.Context, cmd *DeleteDashboardCommand) error {
@ -172,23 +127,6 @@ func (_m *FakeDashboardStore) DeleteDashboardsInFolders(ctx context.Context, req
return r0
}
// DeleteOrphanedProvisionedDashboards provides a mock function with given fields: ctx, cmd
func (_m *FakeDashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error {
ret := _m.Called(ctx, cmd)
if len(ret) == 0 {
panic("no return value specified for DeleteOrphanedProvisionedDashboards")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *DeleteOrphanedProvisionedDashboardsCommand) error); ok {
r0 = rf(ctx, cmd)
} else {
r0 = ret.Error(0)
}
return r0
}
// FindDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
@ -310,66 +248,6 @@ func (_m *FakeDashboardStore) GetDashboardTags(ctx context.Context, query *GetDa
return r0, r1
}
// GetDashboardUIDByID provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) GetDashboardUIDByID(ctx context.Context, query *GetDashboardRefByIDQuery) (*DashboardRef, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboardUIDByID")
}
var r0 *DashboardRef
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardRefByIDQuery) (*DashboardRef, error)); ok {
return rf(ctx, query)
}
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardRefByIDQuery) *DashboardRef); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*DashboardRef)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *GetDashboardRefByIDQuery) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) GetDashboards(ctx context.Context, query *GetDashboardsQuery) ([]*Dashboard, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboards")
}
var r0 []*Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardsQuery) ([]*Dashboard, error)); ok {
return rf(ctx, query)
}
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardsQuery) []*Dashboard); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*Dashboard)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *GetDashboardsQuery) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetDashboardsByLibraryPanelUID provides a mock function with given fields: ctx, libraryPanelUID, orgID
func (_m *FakeDashboardStore) GetDashboardsByLibraryPanelUID(ctx context.Context, libraryPanelUID string, orgID int64) ([]*DashboardRef, error) {
ret := _m.Called(ctx, libraryPanelUID, orgID)

View File

@ -4,33 +4,18 @@ import (
"context"
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/require"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/serverlock"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/dashboards"
dashdb "github.com/grafana/grafana/pkg/services/dashboards/database"
dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
dashsnapdb "github.com/grafana/grafana/pkg/services/dashboardsnapshots/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/secrets/database"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
@ -81,7 +66,6 @@ func TestIntegrationDashboardSnapshotsService(t *testing.T) {
require.Equal(t, rawDashboard, decrypted)
})
t.Run("get dashboard snapshot should return the dashboard decrypted", func(t *testing.T) {
ctx := context.Background()
@ -99,69 +83,3 @@ func TestIntegrationDashboardSnapshotsService(t *testing.T) {
require.Equal(t, rawDashboard, decrypted)
})
}
func TestIntegrationValidateDashboardExists(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
sqlStore := db.InitTestDB(t)
cfg := setting.NewCfg()
dsStore := dashsnapdb.ProvideStore(sqlStore, cfg)
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
feats := featuremgmt.WithFeatures()
dashboardStore, err := dashdb.ProvideDashboardStore(sqlStore, cfg, feats, tagimpl.ProvideService(sqlStore))
require.NoError(t, err)
dashSvc, err := dashsvc.ProvideDashboardServiceImpl(
cfg,
dashboardStore,
folderimpl.ProvideDashboardFolderStore(sqlStore),
feats,
nil,
actest.FakeAccessControl{},
actest.FakeService{},
foldertest.NewFakeService(),
nil,
client.MockTestRestConfig{},
nil,
quotatest.New(false, nil),
nil,
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()),
kvstore.NewFakeKVStore(),
)
require.NoError(t, err)
s := ProvideService(dsStore, secretsService, dashSvc)
ctx := context.Background()
t.Run("returns false when dashboard does not exist", func(t *testing.T) {
err := s.ValidateDashboardExists(ctx, 1, "test")
require.Error(t, err)
require.Equal(t, dashboards.ErrDashboardNotFound, err)
})
t.Run("returns true when dashboard exists", func(t *testing.T) {
err := createDashboard(sqlStore)
require.NoError(t, err)
err = s.ValidateDashboardExists(ctx, 1, "test")
require.NoError(t, err)
})
}
func createDashboard(store db.DB) error {
return store.WithDbSession(context.Background(), func(sess *db.Session) error {
dashboard := &dashboards.Dashboard{
ID: 1,
UID: "test",
OrgID: 1,
Created: time.Now(),
Updated: time.Now(),
}
_, err := sess.Insert(dashboard)
return err
})
}

View File

@ -0,0 +1,75 @@
package dashboardsnapshots
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
"github.com/grafana/grafana/pkg/infra/log"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/web"
)
func TestCreateDashboardSnapshot_DashboardNotFound(t *testing.T) {
mockService := &MockService{}
cfg := dashboardsnapshot.SnapshotSharingOptions{
SnapshotsEnabled: true,
ExternalEnabled: false,
}
testUser := &user.SignedInUser{
UserID: 1,
OrgID: 1,
Login: "testuser",
Name: "Test User",
Email: "test@example.com",
}
dashboard := &common.Unstructured{}
dashboardData := map[string]interface{}{
"uid": "test-dashboard-uid",
"id": 123,
}
dashboardBytes, _ := json.Marshal(dashboardData)
_ = json.Unmarshal(dashboardBytes, dashboard)
cmd := CreateDashboardSnapshotCommand{
DashboardCreateCommand: dashboardsnapshot.DashboardCreateCommand{
Dashboard: dashboard,
Name: "Test Snapshot",
},
}
mockService.On("ValidateDashboardExists", mock.Anything, int64(1), "test-dashboard-uid").
Return(dashboards.ErrDashboardNotFound)
req, _ := http.NewRequest("POST", "/api/snapshots", nil)
req = req.WithContext(identity.WithRequester(req.Context(), testUser))
recorder := httptest.NewRecorder()
ctx := &contextmodel.ReqContext{
Context: &web.Context{
Req: req,
Resp: web.NewResponseWriter("POST", recorder),
},
SignedInUser: testUser,
Logger: log.NewNopLogger(),
}
CreateDashboardSnapshot(ctx, cfg, cmd, mockService)
mockService.AssertExpectations(t)
assert.Equal(t, http.StatusBadRequest, recorder.Code)
var response map[string]interface{}
err := json.Unmarshal(recorder.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, "Dashboard not found", response["message"])
}

View File

@ -89,7 +89,6 @@ func (s *Service) Get(ctx context.Context, query *dashver.GetDashboardVersionQue
query.DashboardID = id
}
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
version, err := s.getHistoryThroughK8s(ctx, query.OrgID, query.DashboardUID, query.Version)
if err != nil {
return nil, err
@ -97,14 +96,6 @@ func (s *Service) Get(ctx context.Context, query *dashver.GetDashboardVersionQue
return version, nil
}
version, err := s.store.Get(ctx, query)
if err != nil {
return nil, err
}
version.Data.Set("id", version.DashboardID)
return version.ToDTO(query.DashboardUID), nil
}
func (s *Service) DeleteExpired(ctx context.Context, cmd *dashver.DeleteExpiredVersionsCommand) error {
versionsToKeep := s.cfg.DashboardVersionsToKeep
if versionsToKeep < 1 {
@ -160,7 +151,6 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
query.Limit = 1000
}
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
versions, err := s.listHistoryThroughK8s(
ctx,
query.OrgID,
@ -174,19 +164,6 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
return versions, nil
}
dvs, err := s.store.List(ctx, query)
if err != nil {
return nil, err
}
dtos := make([]*dashver.DashboardVersionDTO, len(dvs))
for i, v := range dvs {
dtos[i] = v.ToDTO(query.DashboardUID)
}
return &dashver.DashboardVersionResponse{
Versions: dtos,
}, nil
}
// getDashUIDMaybeEmpty is a helper function which takes a dashboardID and
// returns the UID. If the dashboard is not found, it will return an empty
// string.

View File

@ -15,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
@ -25,29 +24,12 @@ import (
)
func TestDashboardVersionService(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
t.Run("Get dashboard version", func(t *testing.T) {
dashboard := &dashver.DashboardVersion{
ID: 11,
Data: &simplejson.Json{},
}
dashboardVersionStore.ExpectedDashboardVersion = dashboard
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(&dashboards.Dashboard{ID: 42}, nil)
dashboardVersion, err := dashboardVersionService.Get(context.Background(), &dashver.GetDashboardVersionQuery{})
require.NoError(t, err)
require.Equal(t, dashboard.ToDTO("uid"), dashboardVersion)
})
t.Run("Get dashboard versions through k8s", func(t *testing.T) {
t.Run("Get dashboard versions", func(t *testing.T) {
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
mockCli := new(client.MockK8sHandler)
dashboardVersionService.k8sclient = mockCli
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
dashboardVersionService.features = featuremgmt.WithFeatures()
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil)
creationTimestamp := time.Now().Add(time.Hour * -24).UTC()
@ -133,7 +115,7 @@ func TestDashboardVersionService(t *testing.T) {
dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
mockCli := new(client.MockK8sHandler)
dashboardVersionService.k8sclient = mockCli
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
dashboardVersionService.features = featuremgmt.WithFeatures()
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil)
mockCli.On("List", mock.Anything, int64(1), mock.Anything).Return(nil, apierrors.NewNotFound(schema.GroupResource{Group: "dashboards.dashboard.grafana.app", Resource: "dashboard"}, "uid"))
@ -176,93 +158,12 @@ func TestDeleteExpiredVersions(t *testing.T) {
}
func TestListDashboardVersions(t *testing.T) {
t.Run("List all versions for a given Dashboard ID", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{
{ID: 1, DashboardID: 42},
}
dashboardService.On("GetDashboardUIDByID", mock.Anything,
mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
Return(&dashboards.DashboardRef{UID: "uid"}, nil)
query := dashver.ListDashboardVersionsQuery{DashboardID: 42}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, 1, len(res.Versions))
// validate that the UID was populated
require.EqualValues(t, &dashver.DashboardVersionResponse{Versions: []*dashver.DashboardVersionDTO{{ID: 1, DashboardID: 42, DashboardUID: "uid"}}}, res)
})
t.Run("List all versions for a non-existent DashboardID", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, log: log.NewNopLogger(), features: featuremgmt.WithFeatures()}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{
{ID: 1, DashboardID: 42},
}
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
Return(nil, dashboards.ErrDashboardNotFound).Once()
query := dashver.ListDashboardVersionsQuery{DashboardID: 42}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, 1, len(res.Versions))
// The DashboardID remains populated with the given value, even though the dash was not found
require.EqualValues(t, &dashver.DashboardVersionResponse{Versions: []*dashver.DashboardVersionDTO{{ID: 1, DashboardID: 42}}}, res)
})
t.Run("List all versions for a given DashboardUID", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, log: log.NewNopLogger(), features: featuremgmt.WithFeatures()}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{{DashboardID: 42, ID: 1}}
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).
Return(&dashboards.Dashboard{ID: 42}, nil)
query := dashver.ListDashboardVersionsQuery{DashboardUID: "uid"}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, 1, len(res.Versions))
// validate that the dashboardID was populated from the GetDashboard method call.
require.EqualValues(t, &dashver.DashboardVersionResponse{Versions: []*dashver.DashboardVersionDTO{{ID: 1, DashboardID: 42, DashboardUID: "uid"}}}, res)
})
t.Run("List all versions for a given non-existent DashboardUID", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, log: log.NewNopLogger(), features: featuremgmt.WithFeatures()}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{{DashboardID: 42, ID: 1}}
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).
Return(nil, dashboards.ErrDashboardNotFound)
query := dashver.ListDashboardVersionsQuery{DashboardUID: "uid"}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, 1, len(res.Versions))
// validate that the dashboardUID & ID are populated, even though the dash was not found
require.EqualValues(t, &dashver.DashboardVersionResponse{Versions: []*dashver.DashboardVersionDTO{{ID: 1, DashboardID: 42, DashboardUID: "uid"}}}, res)
})
t.Run("List Dashboard versions - error from store", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, log: log.NewNopLogger(), features: featuremgmt.WithFeatures()}
dashboardVersionStore.ExpectedError = dashver.ErrDashboardVersionNotFound
query := dashver.ListDashboardVersionsQuery{DashboardID: 42, DashboardUID: "42"}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, res)
require.ErrorIs(t, err, dashver.ErrDashboardVersionNotFound)
})
t.Run("List all versions for a given Dashboard ID through k8s", func(t *testing.T) {
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
mockCli := new(client.MockK8sHandler)
dashboardVersionService.k8sclient = mockCli
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
dashboardVersionService.features = featuremgmt.WithFeatures()
dashboardService.On("GetDashboardUIDByID", mock.Anything,
mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
@ -301,7 +202,7 @@ func TestListDashboardVersions(t *testing.T) {
dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
mockCli := new(client.MockK8sHandler)
dashboardVersionService.k8sclient = mockCli
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
dashboardVersionService.features = featuremgmt.WithFeatures()
dashboardService.On("GetDashboardUIDByID", mock.Anything,
mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
Return(&dashboards.DashboardRef{UID: "uid"}, nil)

View File

@ -483,13 +483,6 @@ var (
Owner: grafanaAppPlatformSquad,
FrontendOnly: true,
},
{
Name: "kubernetesClientDashboardsFolders",
Description: "Route the folder and dashboard service requests to k8s",
Stage: FeatureStageGeneralAvailability,
Owner: grafanaAppPlatformSquad,
Expression: "true", // enabled by default
},
{
Name: "dashboardDisableSchemaValidationV1",
Description: "Disable schema validation for dashboards/v1",

View File

@ -440,7 +440,6 @@ alertingConversionAPI,2025-02-12T07:13:21Z,2025-04-05T08:27:02Z,9593e51da7c05ed5
alertingJiraIntegration,2025-02-14T12:22:04Z,,af8cab92109ecfbb54625ff25a3b90a4f92dc46a,Sonia Aguilar
alertingRuleVersionHistoryRestore,2025-02-17T12:25:32Z,,2014d27defe5668ba07913cec6f2186c90eaaab2,Sonia Aguilar
newShareReportDrawer,2025-02-17T19:05:46Z,,9df6412e92559f2329c2baac0025f56fc57c5bf9,Ezequiel Victorero
kubernetesClientDashboardsFolders,2025-02-18T23:11:26Z,,3e6f40c87386984be60e391b26a7559e3469dac3,Stephanie Hingtgen
managedDualWriter,2025-02-19T14:50:39Z,,5a40c84568485da55ccb42998c6d291d310abd56,Ryan McKinley
rendererDisableAppPluginsPreload,2025-02-24T14:43:06Z,,608d974585c696253ac629f3c7bfc3a0043cbd49,Agnès Toulet
assetSriChecks,2025-03-04T10:56:35Z,,bbfeb8d220cc67c329aa2b5d6ed693ae1cb54325,Jack Westbrook

1 #name created deleted hash author
440 alertingJiraIntegration 2025-02-14T12:22:04Z af8cab92109ecfbb54625ff25a3b90a4f92dc46a Sonia Aguilar
441 alertingRuleVersionHistoryRestore 2025-02-17T12:25:32Z 2014d27defe5668ba07913cec6f2186c90eaaab2 Sonia Aguilar
442 newShareReportDrawer 2025-02-17T19:05:46Z 9df6412e92559f2329c2baac0025f56fc57c5bf9 Ezequiel Victorero
kubernetesClientDashboardsFolders 2025-02-18T23:11:26Z 3e6f40c87386984be60e391b26a7559e3469dac3 Stephanie Hingtgen
443 managedDualWriter 2025-02-19T14:50:39Z 5a40c84568485da55ccb42998c6d291d310abd56 Ryan McKinley
444 rendererDisableAppPluginsPreload 2025-02-24T14:43:06Z 608d974585c696253ac629f3c7bfc3a0043cbd49 Agnès Toulet
445 assetSriChecks 2025-03-04T10:56:35Z bbfeb8d220cc67c329aa2b5d6ed693ae1cb54325 Jack Westbrook

View File

@ -62,7 +62,6 @@ kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,
kubernetesLibraryPanels,experimental,@grafana/grafana-app-platform-squad,false,true,false
kubernetesLibraryPanelConnections,experimental,@grafana/grafana-app-platform-squad,false,true,false
kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true
kubernetesClientDashboardsFolders,GA,@grafana/grafana-app-platform-squad,false,false,false
dashboardDisableSchemaValidationV1,experimental,@grafana/grafana-app-platform-squad,false,false,false
dashboardDisableSchemaValidationV2,experimental,@grafana/grafana-app-platform-squad,false,false,false
dashboardSchemaValidationLogging,experimental,@grafana/grafana-app-platform-squad,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
62 kubernetesLibraryPanels experimental @grafana/grafana-app-platform-squad false true false
63 kubernetesLibraryPanelConnections experimental @grafana/grafana-app-platform-squad false true false
64 kubernetesDashboards experimental @grafana/grafana-app-platform-squad false false true
kubernetesClientDashboardsFolders GA @grafana/grafana-app-platform-squad false false false
65 dashboardDisableSchemaValidationV1 experimental @grafana/grafana-app-platform-squad false false false
66 dashboardDisableSchemaValidationV2 experimental @grafana/grafana-app-platform-squad false false false
67 dashboardSchemaValidationLogging experimental @grafana/grafana-app-platform-squad false false false

View File

@ -259,10 +259,6 @@ const (
// Use the kubernetes API in the frontend for dashboards
FlagKubernetesDashboards = "kubernetesDashboards"
// FlagKubernetesClientDashboardsFolders
// Route the folder and dashboard service requests to k8s
FlagKubernetesClientDashboardsFolders = "kubernetesClientDashboardsFolders"
// FlagDashboardDisableSchemaValidationV1
// Disable schema validation for dashboards/v1
FlagDashboardDisableSchemaValidationV1 = "dashboardDisableSchemaValidationV1"

View File

@ -1729,19 +1729,6 @@
"hideFromDocs": true
}
},
{
"metadata": {
"name": "kubernetesClientDashboardsFolders",
"resourceVersion": "1753448760331",
"creationTimestamp": "2025-02-18T23:11:26Z"
},
"spec": {
"description": "Route the folder and dashboard service requests to k8s",
"stage": "GA",
"codeowner": "@grafana/grafana-app-platform-squad",
"expression": "true"
}
},
{
"metadata": {
"name": "kubernetesDashboards",

View File

@ -112,7 +112,6 @@ func ProvideService(
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
k8sHandler := client.NewK8sHandler(
dual,
request.GetNamespaceMapper(cfg),
@ -129,9 +128,7 @@ func ProvideService(
srv.unifiedStore = unifiedStore
srv.k8sclient = k8sHandler
}
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashHandler := client.NewK8sHandler(
dual,
request.GetNamespaceMapper(cfg),
@ -144,7 +141,6 @@ func ProvideService(
features,
)
srv.dashboardK8sClient = dashHandler
}
return srv
}
@ -201,33 +197,23 @@ func (s *Service) DBMigration(db db.DB) {
func (s *Service) CountFoldersInOrg(ctx context.Context, orgID int64) (int64, error) {
ctx, span := s.tracer.Start(ctx, "folder.CountFoldersInOrg")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.unifiedStore.CountInOrg(ctx, orgID)
}
return s.store.CountInOrg(ctx, orgID)
}
func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery) (model.HitList, error) {
ctx, span := s.tracer.Start(ctx, "folder.SearchFolders")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// TODO:
// - implement filtering by alerting folders and k6 folders (see the dashboards store `FindDashboards` method for reference)
// - implement fallback on search client in unistore to go to legacy store (will need to read from dashboard store)
return s.searchFoldersFromApiServer(ctx, q)
}
return nil, fmt.Errorf("cannot be called on the legacy folder service")
}
func (s *Service) GetFolders(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetFolders")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getFoldersFromApiServer(ctx, q)
}
return s.GetFoldersLegacy(ctx, q)
}
func (s *Service) GetFoldersLegacy(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetFoldersLegacy")
@ -286,11 +272,8 @@ func (s *Service) GetFoldersLegacy(ctx context.Context, q folder.GetFoldersQuery
func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.Get")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getFromApiServer(ctx, q)
}
return s.GetLegacy(ctx, q)
}
func (s *Service) GetLegacy(ctx context.Context, q *folder.GetFolderQuery) (*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetLegacy")
@ -377,11 +360,8 @@ func (s *Service) setFullpath(ctx context.Context, f *folder.Folder, forceLegacy
func (s *Service) GetChildren(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.FolderReference, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetChildren")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getChildrenFromApiServer(ctx, q)
}
return s.GetChildrenLegacy(ctx, q)
}
func (s *Service) GetChildrenLegacy(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.FolderReference, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetChildrenLegacy")
@ -660,11 +640,8 @@ func (s *Service) deduplicateAvailableFolders(ctx context.Context, folders []*fo
func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetParents")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getParentsFromApiServer(ctx, q)
}
return s.GetParentsLegacy(ctx, q)
}
func (s *Service) GetParentsLegacy(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetParentsLegacy")
@ -679,11 +656,8 @@ func (s *Service) GetParentsLegacy(ctx context.Context, q folder.GetParentsQuery
}
func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.createOnApiServer(ctx, cmd)
}
return s.CreateLegacy(ctx, cmd)
}
func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
if cmd.SignedInUser == nil || cmd.SignedInUser.IsNil() {
@ -800,11 +774,8 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm
func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.Update")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.updateOnApiServer(ctx, cmd)
}
return s.UpdateLegacy(ctx, cmd)
}
func (s *Service) UpdateLegacy(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.UpdateLegacy")
@ -943,11 +914,8 @@ func prepareForUpdate(dashFolder *dashboards.Dashboard, orgId int64, userId int6
func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
ctx, span := s.tracer.Start(ctx, "folder.Delete")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.deleteFromApiServer(ctx, cmd)
}
return s.DeleteLegacy(ctx, cmd)
}
func (s *Service) DeleteLegacy(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
ctx, span := s.tracer.Start(ctx, "folder.DeleteLegacy")
@ -1067,11 +1035,8 @@ func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderComm
func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.Move")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.moveOnApiServer(ctx, cmd)
}
return s.MoveLegacy(ctx, cmd)
}
func (s *Service) MoveLegacy(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.MoveLegacy")
@ -1296,13 +1261,9 @@ func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFold
func (s *Service) GetDescendantCounts(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetDescendantCounts")
defer span.End()
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getDescendantCountsFromApiServer(ctx, q)
}
return s.GetDescendantCountsLegacy(ctx, q)
}
func (s *Service) GetDescendantCountsLegacy(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
ctx, span := s.tracer.Start(ctx, "folder.GetDescendantCountsLegacy")
defer span.End()

View File

@ -193,9 +193,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
ExpectedUser: &user.User{},
}
featuresArr := []any{
featuremgmt.FlagKubernetesClientDashboardsFolders}
features := featuremgmt.WithFeatures(featuresArr...)
features := featuremgmt.WithFeatures()
tracer := noop.NewTracerProvider().Tracer("TestIntegrationFolderServiceViaUnifiedStorage")
dashboardStore := dashboards.NewFakeDashboardStore(t)
@ -521,7 +519,7 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
tracer := noop.NewTracerProvider().Tracer("TestSearchFoldersFromApiServer")
service := Service{
k8sclient: fakeK8sClient,
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
features: featuremgmt.WithFeatures(),
unifiedStore: folderStore,
tracer: tracer,
accessControl: actest.FakeAccessControl{ExpectedEvaluate: true},
@ -764,7 +762,7 @@ func TestGetFoldersFromApiServer(t *testing.T) {
tracer := noop.NewTracerProvider().Tracer("TestGetFoldersFromApiServer")
service := Service{
k8sclient: fakeK8sClient,
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
features: featuremgmt.WithFeatures(),
unifiedStore: folderStore,
accessControl: actest.FakeAccessControl{ExpectedEvaluate: true},
tracer: tracer,
@ -866,7 +864,7 @@ func TestIntegrationDeleteFoldersFromApiServer(t *testing.T) {
publicDashboardService: publicDashboardFakeService,
accessControl: actest.FakeAccessControl{ExpectedEvaluate: true},
registry: make(map[string]folder.RegistryService),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
features: featuremgmt.WithFeatures(),
tracer: tracer,
}
user := &user.SignedInUser{OrgID: 1}

View File

@ -15,16 +15,49 @@ type FakeService struct {
ExpectedError error
ExpectedDescendantCounts map[string]int64
LastQuery folder.GetFoldersQuery
foldersByUID map[string]*folder.Folder
}
func NewFakeService() *FakeService {
return &FakeService{}
return &FakeService{
foldersByUID: make(map[string]*folder.Folder),
}
}
func (s *FakeService) AddFolder(f *folder.Folder) {
if s.foldersByUID == nil {
s.foldersByUID = make(map[string]*folder.Folder)
}
s.foldersByUID[f.UID] = f
s.ExpectedFolders = append(s.ExpectedFolders, f)
}
func (s *FakeService) SetFolders(folders map[string]*folder.Folder) {
s.foldersByUID = folders
s.ExpectedFolders = make([]*folder.Folder, 0, len(folders))
for _, f := range folders {
s.ExpectedFolders = append(s.ExpectedFolders, f)
}
}
var _ folder.Service = (*FakeService)(nil)
func (s *FakeService) GetChildren(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.FolderReference, error) {
return s.ExpectedFoldersRef, s.ExpectedError
if s.ExpectedError != nil {
return nil, s.ExpectedError
}
if s.ExpectedFoldersRef != nil {
return s.ExpectedFoldersRef, nil
}
var result []*folder.FolderReference
for _, f := range s.ExpectedFolders {
if f.OrgID == q.OrgID && f.ParentUID == q.UID {
result = append(result, f.ToFolderReference())
}
}
return result, nil
}
func (s *FakeService) GetChildrenLegacy(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.FolderReference, error) {
return s.ExpectedFoldersRef, s.ExpectedError
@ -45,6 +78,11 @@ func (s *FakeService) CreateLegacy(ctx context.Context, cmd *folder.CreateFolder
}
func (s *FakeService) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Folder, error) {
if q.UID != nil && s.foldersByUID != nil {
if f, exists := s.foldersByUID[*q.UID]; exists {
return f, nil
}
}
return s.ExpectedFolder, s.ExpectedError
}
func (s *FakeService) GetLegacy(ctx context.Context, q *folder.GetFolderQuery) (*folder.Folder, error) {
@ -84,7 +122,23 @@ func (s *FakeService) GetDescendantCountsLegacy(ctx context.Context, q *folder.G
}
func (s *FakeService) GetFolders(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
return s.ExpectedFolders, s.ExpectedError
if s.foldersByUID != nil && len(q.UIDs) > 0 {
var result []*folder.Folder
for _, uid := range q.UIDs {
if f, exists := s.foldersByUID[uid]; exists {
result = append(result, f)
}
}
return result, nil
}
folders := make([]*folder.Folder, 0, len(s.ExpectedFolders))
for _, f := range s.ExpectedFolders {
if f.OrgID == q.OrgID {
folders = append(folders, f)
}
}
return folders, s.ExpectedError
}
func (s *FakeService) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery) (model.HitList, error) {

View File

@ -893,7 +893,6 @@ func getFoldersWithMatchingTitles(c context.Context, l *LibraryElementService, s
return nil, nil
}
if l.features.IsEnabled(c, featuremgmt.FlagKubernetesClientDashboardsFolders) {
searchQuery := folder.SearchFoldersQuery{
OrgID: signedInUser.GetOrgID(),
Title: query.SearchString,
@ -911,21 +910,3 @@ func getFoldersWithMatchingTitles(c context.Context, l *LibraryElementService, s
}
return foldersWithMatchingTitles, nil
}
// Fallback to GetFolders
fs, err := l.folderService.GetFolders(c, folder.GetFoldersQuery{
OrgID: signedInUser.GetOrgID(),
SignedInUser: signedInUser,
})
if err != nil {
return nil, err
}
foldersWithMatchingTitles := make([]string, 0, len(fs))
for _, f := range fs {
if strings.Contains(strings.ToLower(f.Title), strings.ToLower(query.SearchString)) {
foldersWithMatchingTitles = append(foldersWithMatchingTitles, f.UID)
}
}
return foldersWithMatchingTitles, nil
}

View File

@ -46,8 +46,8 @@ func TestIntegration_CreateLibraryElement(t *testing.T) {
},
Version: 1,
Meta: model.LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: "uid_for_ScenarioFolder",
FolderName: sc.folder.Title,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: sc.initialResult.Result.Meta.Created,
Updated: sc.initialResult.Result.Meta.Updated,
@ -97,8 +97,8 @@ func TestIntegration_CreateLibraryElement(t *testing.T) {
},
Version: 1,
Meta: model.LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: "uid_for_ScenarioFolder",
FolderName: sc.folder.Title,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Meta.Created,
Updated: result.Result.Meta.Updated,
@ -176,8 +176,8 @@ func TestIntegration_CreateLibraryElement(t *testing.T) {
},
Version: 1,
Meta: model.LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderUID: "uid_for_ScenarioFolder",
FolderName: sc.folder.Title,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: result.Result.Meta.Created,
Updated: result.Result.Meta.Updated,

View File

@ -4,9 +4,9 @@ import (
"encoding/json"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
@ -25,6 +25,7 @@ func TestIntegration_DeleteLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to delete a library panel that exists, it should succeed and return correct ID",
func(t *testing.T, sc scenarioContext) {
sc.dashboardSvc.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil)
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.deleteHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -47,39 +48,14 @@ func TestIntegration_DeleteLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to delete a library panel that is connected, it should fail",
func(t *testing.T, sc scenarioContext) {
dashJSON := map[string]any{
"panels": []any{
map[string]any{
"id": int64(1),
"gridPos": map[string]any{
"h": 6,
"w": 6,
"x": 0,
"y": 0,
sc.dashboardSvc.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
{
ID: 1,
UID: "test",
},
},
map[string]any{
"id": int64(2),
"gridPos": map[string]any{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"libraryPanel": map[string]any{
"uid": sc.initialResult.Result.UID,
"name": sc.initialResult.Result.Name,
},
},
},
}
dash := dashboards.Dashboard{
Title: "Testing deleteHandler ",
Data: simplejson.NewFromAny(dashJSON),
}
}, nil)
// nolint:staticcheck
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, 1)
require.NoError(t, err)
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
@ -89,6 +65,7 @@ func TestIntegration_DeleteLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to delete a library panel that is connected to a non-existent dashboard, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.dashboardSvc.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, 9999999)
require.NoError(t, err)

View File

@ -8,8 +8,10 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
searchmodel "github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/search/sort"
)
@ -406,7 +408,14 @@ func TestIntegration_GetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilterUIDs is set to existing folders, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) {
newFolder := createFolder(t, sc, "NewFolder", sc.folderSvc)
newFolder := &folder.Folder{
ID: 2,
OrgID: 1,
UID: "uid_for_NewFolder",
Title: "NewFolder",
}
sc.folderSvc.ExpectedFolder = newFolder
sc.folderSvc.ExpectedFolders = []*folder.Folder{newFolder}
// nolint:staticcheck
command := getCreatePanelCommand(newFolder.ID, newFolder.UID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command)
@ -448,7 +457,7 @@ func TestIntegration_GetAllLibraryElements(t *testing.T) {
},
Version: 1,
Meta: model.LibraryElementDTOMeta{
FolderName: "NewFolder",
FolderName: newFolder.Title,
FolderUID: newFolder.UID,
ConnectedDashboards: 0,
Created: result.Result.Elements[0].Meta.Created,
@ -1171,6 +1180,14 @@ func TestIntegration_GetAllLibraryElements(t *testing.T) {
// Folder name search integration tests
scenarioWithPanel(t, "When searching by folder name, it should return panels in that folder",
func(t *testing.T, sc scenarioContext) {
sc.folderSvc.ExpectedHitList = searchmodel.HitList{
{
UID: sc.folder.UID,
Title: sc.folder.Title,
Type: searchmodel.DashHitFolder,
},
}
// Create a panel in the existing folder
// nolint:staticcheck
command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Panel in ScenarioFolder")
@ -1275,6 +1292,14 @@ func TestIntegration_GetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When searching by partial folder name, it should return panels in matching folders",
func(t *testing.T, sc scenarioContext) {
sc.folderSvc.ExpectedHitList = searchmodel.HitList{
{
UID: sc.folder.UID,
Title: sc.folder.Title,
Type: searchmodel.DashHitFolder,
},
}
// Create a panel in the existing folder
// nolint:staticcheck
command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Test Panel")

View File

@ -7,7 +7,6 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/services/dashboards"
@ -57,7 +56,7 @@ func TestIntegration_GetLibraryElement(t *testing.T) {
},
Version: 1,
Meta: model.LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderName: sc.folder.Title,
FolderUID: sc.folder.UID,
ConnectedDashboards: 0,
Created: res.Result.Meta.Created,
@ -120,6 +119,8 @@ func TestIntegration_GetLibraryElement(t *testing.T) {
SignedInUser: sc.reqContext.SignedInUser,
})
require.NoError(t, err)
sc.folderSvc.ExpectedFolder = nil
sc.folderSvc.ExpectedError = folder.ErrFolderNotFound
err = sc.sqlStore.WithDbSession(sc.reqContext.Req.Context(), func(session *db.Session) error {
elem, err := sc.service.GetLibraryElement(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, session, result.UID)
require.NoError(t, err)
@ -131,39 +132,7 @@ func TestIntegration_GetLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get a connected library panel, it should succeed and return correct connected dashboards",
func(t *testing.T, sc scenarioContext) {
dashJSON := map[string]any{
"panels": []any{
map[string]any{
"id": int64(1),
"gridPos": map[string]any{
"h": 6,
"w": 6,
"x": 0,
"y": 0,
},
},
map[string]any{
"id": int64(2),
"gridPos": map[string]any{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"libraryPanel": map[string]any{
"uid": sc.initialResult.Result.UID,
"name": sc.initialResult.Result.Name,
},
},
},
}
dash := dashboards.Dashboard{
Title: "Testing getHandler",
Data: simplejson.NewFromAny(dashJSON),
}
// nolint:staticcheck
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, 1)
require.NoError(t, err)
expected := func(res libraryElementResult) libraryElementResult {
@ -187,7 +156,7 @@ func TestIntegration_GetLibraryElement(t *testing.T) {
},
Version: 1,
Meta: model.LibraryElementDTOMeta{
FolderName: "ScenarioFolder",
FolderName: sc.folder.Title,
FolderUID: sc.folder.UID,
ConnectedDashboards: 1,
Created: res.Result.Meta.Created,

View File

@ -4,6 +4,7 @@ import (
"testing"
"github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/util"
@ -28,7 +29,14 @@ func TestIntegration_PatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel that exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
newFolder := createFolder(t, sc, "NewFolder", sc.folderSvc)
newFolder := &folder.Folder{
ID: 2,
OrgID: 1,
UID: "uid_for_NewFolder",
Title: "NewFolder",
}
sc.folderSvc.ExpectedFolder = newFolder
sc.folderSvc.ExpectedFolders = []*folder.Folder{newFolder}
cmd := model.PatchLibraryElementCommand{
FolderID: newFolder.ID, // nolint:staticcheck
FolderUID: &newFolder.UID,
@ -95,7 +103,14 @@ func TestIntegration_PatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel with folder only, it should change folder successfully and return correct result",
func(t *testing.T, sc scenarioContext) {
newFolder := createFolder(t, sc, "NewFolder", sc.folderSvc)
newFolder := &folder.Folder{
ID: 2,
OrgID: 1,
UID: "uid_for_NewFolder",
Title: "NewFolder",
}
sc.folderSvc.ExpectedFolder = newFolder
sc.folderSvc.ExpectedFolders = []*folder.Folder{newFolder}
cmd := model.PatchLibraryElementCommand{
FolderID: newFolder.ID, // nolint:staticcheck
FolderUID: &newFolder.UID,
@ -340,26 +355,35 @@ func TestIntegration_PatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail",
func(t *testing.T, sc scenarioContext) {
newFolder := createFolder(t, sc, "NewFolder", sc.folderSvc)
newFolder := &folder.Folder{
ID: 2,
OrgID: 1,
UID: "uid_for_NewFolder",
Title: "NewFolder",
}
sc.folderSvc.ExpectedFolder = newFolder
sc.folderSvc.ExpectedFolders = []*folder.Folder{newFolder}
// nolint:staticcheck
command := getCreatePanelCommand(newFolder.ID, newFolder.UID, "Text - Library Panel")
sc.ctx.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
var result = validateAndUnMarshalResponse(t, resp)
sc.service.createHandler(sc.reqContext)
cmd := model.PatchLibraryElementCommand{
FolderID: 1, // nolint:staticcheck
FolderUID: &sc.folder.UID,
FolderID: newFolder.ID, // nolint:staticcheck
FolderUID: &newFolder.UID,
Version: 1,
Kind: int64(model.PanelElement),
}
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID})
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
sc.ctx.Req.Body = mockRequestBody(cmd)
resp = sc.service.patchHandler(sc.reqContext)
resp := sc.service.patchHandler(sc.reqContext)
require.Equal(t, 400, resp.Status())
})
scenarioWithPanel(t, "When an admin tries to patch a library panel in another org, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.folderSvc.ExpectedFolder = nil
sc.folderSvc.ExpectedError = folder.ErrFolderNotFound
cmd := model.PatchLibraryElementCommand{
FolderID: sc.folder.ID, // nolint:staticcheck
FolderUID: &sc.folder.UID,

View File

@ -16,26 +16,17 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/serverlock"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/client"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
@ -43,13 +34,9 @@ import (
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/userimpl"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/grafana/grafana/pkg/web"
)
@ -67,39 +54,7 @@ func TestIntegration_DeleteLibraryPanelsInFolder(t *testing.T) {
}
scenarioWithPanel(t, "When an admin tries to delete a folder that contains connected library elements, it should fail",
func(t *testing.T, sc scenarioContext) {
dashJSON := map[string]any{
"panels": []any{
map[string]any{
"id": int64(1),
"gridPos": map[string]any{
"h": 6,
"w": 6,
"x": 0,
"y": 0,
},
},
map[string]any{
"id": int64(2),
"gridPos": map[string]any{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"libraryPanel": map[string]any{
"uid": sc.initialResult.Result.UID,
"name": sc.initialResult.Result.Name,
},
},
},
}
dash := dashboards.Dashboard{
Title: "Testing DeleteLibraryElementsInFolder",
Data: simplejson.NewFromAny(dashJSON),
}
// nolint:staticcheck
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, 1)
require.NoError(t, err)
err = sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.UID)
@ -148,39 +103,7 @@ func TestIntegration_GetLibraryPanelConnections(t *testing.T) {
}
scenarioWithPanel(t, "When an admin tries to get connections of library panel, it should succeed and return correct result",
func(t *testing.T, sc scenarioContext) {
dashJSON := map[string]any{
"panels": []any{
map[string]any{
"id": int64(1),
"gridPos": map[string]any{
"h": 6,
"w": 6,
"x": 0,
"y": 0,
},
},
map[string]any{
"id": int64(2),
"gridPos": map[string]any{
"h": 6,
"w": 6,
"x": 6,
"y": 0,
},
"libraryPanel": map[string]any{
"uid": sc.initialResult.Result.UID,
"name": sc.initialResult.Result.Name,
},
},
},
}
dash := dashboards.Dashboard{
Title: "Testing GetLibraryPanelConnections",
Data: simplejson.NewFromAny(dashJSON),
}
// nolint:staticcheck
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID)
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, 1)
require.NoError(t, err)
// add a connection where the dashboard doesn't exist. Shouldn't be returned in the list
@ -194,8 +117,7 @@ func TestIntegration_GetLibraryPanelConnections(t *testing.T) {
ID: sc.initialResult.Result.ID,
Kind: sc.initialResult.Result.Kind,
ElementID: 1,
ConnectionID: dashInDB.ID,
ConnectionUID: dashInDB.UID,
ConnectionID: 1,
Created: res.Result[0].Created,
CreatedBy: librarypanel.LibraryElementDTOMetaUser{
Id: 1,
@ -207,6 +129,13 @@ func TestIntegration_GetLibraryPanelConnections(t *testing.T) {
}
}
sc.dashboardSvc.On("GetDashboardsByLibraryPanelUID", mock.Anything, mock.Anything, mock.Anything).Return([]*dashboards.DashboardRef{
{
ID: 1,
UID: "",
},
}, nil)
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.getConnectionsHandler(sc.reqContext)
var result = validateAndUnMarshalConnectionResponse(t, resp)
@ -239,13 +168,7 @@ func TestIntegration_GetLibraryPanelConnections(t *testing.T) {
})
require.NoError(t, err)
dash := dashboards.Dashboard{
Title: "Testing create element",
Data: simplejson.NewFromAny(map[string]any{}),
}
// nolint:staticcheck
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID)
err = sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID)
err = sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, 1)
require.NoError(t, err)
})
}
@ -319,77 +242,26 @@ type scenarioContext struct {
initialResult libraryElementResult
sqlStore db.DB
log log.Logger
folderSvc folder.Service
folderSvc *foldertest.FakeService
dashboardSvc *dashboards.FakeDashboardService
}
func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash *dashboards.Dashboard, folderID int64, folderUID string) *dashboards.Dashboard {
// nolint:staticcheck
dash.FolderID = folderID
dash.FolderUID = folderUID
dashItem := &dashboards.SaveDashboardDTO{
Dashboard: dash,
Message: "",
OrgID: user.OrgID,
User: &user,
Overwrite: false,
}
features := featuremgmt.WithFeatures()
cfg := setting.NewCfg()
quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
require.NoError(t, err)
ac := actest.FakeAccessControl{ExpectedEvaluate: true}
folderPermissions := acmock.NewMockedPermissionsService()
folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
dashboardPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fStore := folderimpl.ProvideStore(sqlStore)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
_, err = folderSvc.Create(context.Background(), &folder.CreateFolderCommand{UID: folderUID, SignedInUser: &user, Title: folderUID + "-title"})
require.NoError(t, err)
service, err := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore,
features, folderPermissions, ac,
actest.FakeService{},
folderSvc,
nil,
client.MockTestRestConfig{},
nil,
quotaService,
nil,
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()),
kvstore.NewFakeKVStore(),
)
require.NoError(t, err)
service.RegisterDashboardPermissions(dashboardPermissions)
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
require.NoError(t, err)
return dashboard
}
func createFolder(t *testing.T, sc scenarioContext, title string, folderSvc folder.Service) *folder.Folder {
func createFolder(t *testing.T, sc scenarioContext, title string, folderSvc *foldertest.FakeService) *folder.Folder {
t.Helper()
ctx := identity.WithRequester(context.Background(), &sc.user)
folder, err := folderSvc.Create(ctx, &folder.CreateFolderCommand{
f, err := folderSvc.Create(ctx, &folder.CreateFolderCommand{
OrgID: sc.user.OrgID, Title: title, UID: "uid_for_" + title, SignedInUser: &sc.user,
})
require.NoError(t, err)
folderSvc.ExpectedFolder = f
folderSvc.ExpectedFolders = append(folderSvc.ExpectedFolders, f)
// Set user permissions on the newly created folder so that they can interact with library elements stored in it
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionDashboardsCreate] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionDashboardsCreate], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite], dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID))
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead], dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID))
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionDashboardsCreate] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionDashboardsCreate], dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID))
return folder
return f
}
func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryElementResult {
@ -465,32 +337,28 @@ func setupTestScenario(t *testing.T) scenarioContext {
sqlStore, cfg := db.InitTestDBWithCfg(t)
t.Cleanup(db.CleanupTestDB)
quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
require.NoError(t, err)
ac := acimpl.ProvideAccessControl(features)
folderPermissions := acmock.NewMockedPermissionsService()
folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
dashboardPermissions := acmock.NewMockedPermissionsService()
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fStore := folderimpl.ProvideStore(sqlStore)
publicDash := &publicdashboards.FakePublicDashboardServiceWrapper{}
publicDash.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), publicDash, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
folderSvc := foldertest.NewFakeService()
f := &folder.Folder{
ID: 1,
OrgID: 1,
UID: "uid_for_ScenarioFolder",
Title: "ScenarioFolder",
}
folderSvc.ExpectedFolder = f
folderSvc.ExpectedFolders = []*folder.Folder{f}
dashService := dashboards.NewFakeDashboardService(t)
alertStore, err := ngstore.ProvideDBStore(cfg, features, sqlStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
require.NoError(t, err)
err = folderSvc.RegisterService(alertStore)
require.NoError(t, err)
dashService, dashSvcErr := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore,
features, folderPermissions, ac, actest.FakeService{}, folderSvc,
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()),
kvstore.NewFakeKVStore(),
)
require.NoError(t, dashSvcErr)
dashService.RegisterDashboardPermissions(dashboardPermissions)
service := LibraryElementService{
Cfg: cfg,
features: featuremgmt.WithFeatures(),
@ -531,6 +399,7 @@ func setupTestScenario(t *testing.T) scenarioContext {
SignedInUser: &usr,
},
folderSvc: folderSvc,
dashboardSvc: dashService,
}
sc.folder = createFolder(t, sc, "ScenarioFolder", folderSvc)

View File

@ -7,43 +7,27 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/serverlock"
"github.com/grafana/grafana/pkg/infra/slugify"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/userimpl"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
@ -93,7 +77,7 @@ func TestIntegrationConnectLibraryPanelsForDashboard(t *testing.T) {
Title: "Testing ConnectLibraryPanelsForDashboard",
Data: simplejson.NewFromAny(dashJSON),
}
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash)
dashInDB := createDashboard(t, sc, &dash)
err := sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB)
require.NoError(t, err)
@ -191,7 +175,7 @@ func TestIntegrationConnectLibraryPanelsForDashboard(t *testing.T) {
Title: "Testing ConnectLibraryPanelsForDashboard",
Data: simplejson.NewFromAny(dashJSON),
}
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash)
dashInDB := createDashboard(t, sc, &dash)
err = sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB)
require.NoError(t, err)
@ -237,7 +221,7 @@ func TestIntegrationConnectLibraryPanelsForDashboard(t *testing.T) {
Title: "Testing ConnectLibraryPanelsForDashboard",
Data: simplejson.NewFromAny(dashJSON),
}
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash)
dashInDB := createDashboard(t, sc, &dash)
err := sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB)
require.EqualError(t, err, errLibraryPanelHeaderUIDMissing.Error())
@ -293,7 +277,7 @@ func TestIntegrationConnectLibraryPanelsForDashboard(t *testing.T) {
Title: "Testing ConnectLibraryPanelsForDashboard",
Data: simplejson.NewFromAny(dashJSON),
}
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash)
dashInDB := createDashboard(t, sc, &dash)
err = sc.elementService.ConnectElementsToDashboard(sc.ctx, sc.user, []string{sc.initialResult.Result.UID}, dashInDB.ID)
require.NoError(t, err)
@ -411,7 +395,7 @@ func TestIntegrationImportLibraryPanelsForDashboard(t *testing.T) {
element, err := sc.elementService.GetElement(sc.ctx, sc.user,
model.GetLibraryElementCommand{UID: missingUID, FolderName: dashboards.RootFolderName})
require.NoError(t, err)
var expected = getExpected(t, element, missingUID, missingName, missingModel)
var expected = getExpected(t, element, missingUID, missingName, missingModel, "Test Folder")
var result = toLibraryElement(t, element)
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
@ -452,7 +436,7 @@ func TestIntegrationImportLibraryPanelsForDashboard(t *testing.T) {
element, err := sc.elementService.GetElement(sc.ctx, sc.user,
model.GetLibraryElementCommand{UID: existingUID, FolderName: dashboards.RootFolderName})
require.NoError(t, err)
var expected = getExpected(t, element, existingUID, existingName, sc.initialResult.Result.Model)
var expected = getExpected(t, element, existingUID, existingName, sc.initialResult.Result.Model, "Test Folder")
expected.FolderUID = sc.initialResult.Result.FolderUID
expected.Description = sc.initialResult.Result.Description
expected.Meta.FolderUID = sc.folder.UID
@ -567,7 +551,7 @@ func TestIntegrationImportLibraryPanelsForDashboard(t *testing.T) {
element, err := sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: outsideUID, FolderName: dashboards.RootFolderName})
require.NoError(t, err)
expected := getExpected(t, element, outsideUID, outsideName, outsideModel)
expected := getExpected(t, element, outsideUID, outsideName, outsideModel, "Test Folder")
result := toLibraryElement(t, element)
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
@ -575,7 +559,7 @@ func TestIntegrationImportLibraryPanelsForDashboard(t *testing.T) {
element, err = sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: insideUID, FolderName: dashboards.RootFolderName})
require.NoError(t, err)
expected = getExpected(t, element, insideUID, insideName, insideModel)
expected = getExpected(t, element, insideUID, insideName, insideModel, "Test Folder")
result = toLibraryElement(t, element)
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
@ -648,6 +632,8 @@ type scenarioContext struct {
initialResult libraryPanelResult
sqlStore db.DB
lps LibraryPanelService
mockDashboard *dashboards.FakeDashboardService
mockFolder *foldertest.FakeService
}
func toLibraryElement(t *testing.T, res model.LibraryElementDTO) libraryElement {
@ -685,7 +671,7 @@ func toLibraryElement(t *testing.T, res model.LibraryElementDTO) libraryElement
}
}
func getExpected(t *testing.T, res model.LibraryElementDTO, UID string, name string, lEModel map[string]any) libraryElement {
func getExpected(t *testing.T, res model.LibraryElementDTO, UID string, name string, lEModel map[string]any, folderName string) libraryElement {
marshalled, err := json.Marshal(lEModel)
require.NoError(t, err)
var libModel libraryElementModel
@ -704,7 +690,7 @@ func getExpected(t *testing.T, res model.LibraryElementDTO, UID string, name str
Model: libModel,
Version: 1,
Meta: model.LibraryElementDTOMeta{
FolderName: "General",
FolderName: folderName,
FolderUID: res.FolderUID,
ConnectedDashboards: 0,
Created: res.Meta.Created,
@ -722,59 +708,14 @@ func getExpected(t *testing.T, res model.LibraryElementDTO, UID string, name str
},
}
}
func createDashboard(t *testing.T, sc scenarioContext, dash *dashboards.Dashboard) *dashboards.Dashboard {
dash.ID = 1
dash.UID = "test-dashboard-uid"
dash.Created = time.Now()
dash.Updated = time.Now()
dash.Version = 1
func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash *dashboards.Dashboard) *dashboards.Dashboard {
dashItem := &dashboards.SaveDashboardDTO{
Dashboard: dash,
Message: "",
OrgID: user.OrgID,
User: user,
Overwrite: false,
}
features := featuremgmt.WithFeatures()
cfg := setting.NewCfg()
quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
require.NoError(t, err)
ac := actest.FakeAccessControl{ExpectedEvaluate: true}
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
dashPermissionService := acmock.NewMockedPermissionsService()
dashPermissionService.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
service, err := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore,
features, acmock.NewMockedPermissionsService(), ac, actest.FakeService{}, foldertest.NewFakeService(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()),
kvstore.NewFakeKVStore())
require.NoError(t, err)
service.RegisterDashboardPermissions(dashPermissionService)
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
require.NoError(t, err)
return dashboard
}
func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder {
t.Helper()
features := featuremgmt.WithFeatures()
ac := actest.FakeAccessControl{ExpectedEvaluate: true}
cfg := setting.NewCfg()
dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, features, tagimpl.ProvideService(sc.sqlStore))
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
fStore := folderimpl.ProvideStore(sc.sqlStore)
s := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
t.Logf("Creating folder with title and UID %q", title)
ctx := identity.WithRequester(context.Background(), sc.user)
folder, err := s.Create(ctx, &folder.CreateFolderCommand{OrgID: sc.user.OrgID, Title: title, UID: title, SignedInUser: sc.user})
require.NoError(t, err)
return folder
return dash
}
func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
@ -821,8 +762,6 @@ func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, s
})
}
// testScenario is a wrapper around t.Run performing common setup for library panel tests.
// It takes your real test function as a callback.
func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
t.Helper()
@ -832,38 +771,29 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
sqlStore, cfg := db.InitTestDBWithCfg(t)
quotaService := quotatest.New(false, nil)
features := featuremgmt.WithFeatures()
ac := actest.FakeAccessControl{ExpectedEvaluate: true}
dashStore := &dashboards.FakeDashboardStore{}
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
dashPermissionService := acmock.NewMockedPermissionsService()
folderSvc := foldertest.NewFakeService()
folderSvc.ExpectedFolder = &folder.Folder{ID: 1}
dashService, err := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashStore, folderStore,
features, acmock.NewMockedPermissionsService(), ac, actest.FakeService{}, folderSvc,
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()),
kvstore.NewFakeKVStore())
require.NoError(t, err)
dashService.RegisterDashboardPermissions(dashPermissionService)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
require.NoError(t, err)
fStore := folderimpl.ProvideStore(sqlStore)
folderService := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, features, ac, dashService, nil, nil)
mockDashboardService := dashboards.NewFakeDashboardService(t)
mockFolderService := foldertest.NewFakeService()
mockFolder := &folder.Folder{
ID: 1,
UID: "test-folder-uid",
Title: "Test Folder",
URL: "/dashboards/f/test-folder-uid/test-folder",
Version: 0,
Created: time.Now(),
Updated: time.Now(),
UpdatedBy: 0,
CreatedBy: 0,
HasACL: false,
}
mockFolderService.ExpectedFolder = mockFolder
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), mockFolderService, features, ac, mockDashboardService, nil, nil)
service := LibraryPanelService{
Cfg: cfg,
SQLStore: sqlStore,
LibraryElementService: elementService,
FolderService: folderService,
FolderService: mockFolderService,
}
usr := &user.SignedInUser{
UserID: 1,
Name: "Signed In User",
@ -872,17 +802,12 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
OrgID: orgID,
OrgRole: role,
LastSeenAt: time.Now(),
// Allow the user to create folders
Permissions: map[int64]map[string][]string{
orgID: {
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll},
},
},
}
// deliberate difference between signed in user and user in db to make it crystal clear
// what to expect in the tests
// In the real world these are identical
cmd := user.CreateUserCommand{
Email: "user.in.db@test.com",
Name: "User In DB",
@ -898,6 +823,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
require.NoError(t, err)
_, err = usrSvc.Create(context.Background(), &cmd)
require.NoError(t, err)
sc := scenarioContext{
user: usr,
ctx: ctx,
@ -905,21 +831,11 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
elementService: elementService,
sqlStore: sqlStore,
lps: service,
mockDashboard: mockDashboardService,
mockFolder: mockFolderService,
}
foldr := createFolder(t, sc, "ScenarioFolder")
sc.folder = &folder.Folder{
ID: foldr.ID, // nolint:staticcheck
UID: foldr.UID,
Title: foldr.Title,
URL: dashboards.GetFolderURL(foldr.UID, slugify.Slugify(foldr.Title)),
Version: 0,
Created: foldr.Created,
Updated: foldr.Updated,
UpdatedBy: 0,
CreatedBy: 0,
HasACL: false,
}
sc.folder = mockFolder
fn(t, sc)
})
}

View File

@ -571,6 +571,7 @@ func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) {
fldr.ParentUID = ""
folderService.ExpectedFolder = fldr
folderService.ExpectedFolders = []*folder.Folder{fldr}
folderService.AddFolder(fldr)
ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr)
// Create rules in both folders
@ -669,6 +670,8 @@ func TestRouteConvertPrometheusGetNamespace(t *testing.T) {
fldr2 := randFolder()
fldr2.ParentUID = ""
folderService.ExpectedFolders = []*folder.Folder{fldr, fldr2}
folderService.AddFolder(fldr)
folderService.AddFolder(fldr2)
ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr, fldr2)
// Create a Grafana rule for each Prometheus rule
@ -798,6 +801,7 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
// Create a folder in the root
fldr := randFolder()
fldr.ParentUID = ""
folderService.AddFolder(fldr)
folderService.ExpectedFolders = []*folder.Folder{fldr}
ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr)

View File

@ -29,14 +29,10 @@ import (
"github.com/grafana/grafana/pkg/infra/log/logtest"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/apiserver"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
@ -47,14 +43,10 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/ngalert/store"
ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/secrets"
secrets_fakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
@ -353,14 +345,6 @@ func TestIntegrationProvisioningApi(t *testing.T) {
orgID := int64(2)
rule := createTestAlertRule("rule", orgID)
_, err := sut.folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: rule.FolderUID,
Title: "Folder Title",
OrgID: orgID,
SignedInUser: &user.SignedInUser{OrgID: orgID},
})
require.NoError(t, err)
insertRuleInOrg(t, sut, rule, orgID)
rule.FolderUID = "does-not-exist"
@ -462,14 +446,7 @@ func TestIntegrationProvisioningApi(t *testing.T) {
rc.Req.Header = map[string][]string{"X-Disable-Provenance": {"true"}}
rc.OrgID = 3
rule := createTestAlertRule("rule", 1)
_, err := sut.folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: "folder-uid",
Title: "Folder Title",
OrgID: rc.OrgID,
SignedInUser: &user.SignedInUser{OrgID: rc.OrgID},
})
require.NoError(t, err)
rule.FolderUID = "folder-uid3"
response := sut.RoutePostAlertRule(&rc, rule)
@ -486,13 +463,7 @@ func TestIntegrationProvisioningApi(t *testing.T) {
rule.UID = uid
orgID := int64(3)
_, err := sut.folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: "folder-uid",
Title: "Folder Title",
OrgID: orgID,
SignedInUser: &user.SignedInUser{OrgID: orgID},
})
require.NoError(t, err)
rule.FolderUID = "folder-uid3"
insertRuleInOrg(t, sut, rule, orgID)
rc := createTestRequestCtx()
@ -560,14 +531,7 @@ func TestIntegrationProvisioningApi(t *testing.T) {
uid := util.GenerateShortUID()
rule := createTestAlertRule("rule", 3)
rule.UID = uid
_, err := sut.folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: rule.FolderUID,
Title: "Folder Title",
OrgID: rule.OrgID,
SignedInUser: &user.SignedInUser{OrgID: rule.OrgID},
})
require.NoError(t, err)
rule.FolderUID = "folder-uid3"
insertRuleInOrg(t, sut, rule, 3)
@ -2053,7 +2017,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: string(raw),
})
sqlStore, cfg := db.InitTestDBWithCfg(t)
sqlStore, _ := db.InitTestDBWithCfg(t)
quotas := &provisioning.MockQuotaChecker{}
quotas.EXPECT().LimitOK()
@ -2077,14 +2041,39 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
}}, nil).Maybe()
ac := &recordingAccessControlFake{}
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fStore := folderimpl.ProvideStore(sqlStore)
folderService := folderimpl.ProvideService(
fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
folderService := foldertest.NewFakeService()
folder1 := &folder.Folder{
UID: "folder-uid",
Title: "Folder Title",
Fullpath: "Folder Title",
OrgID: 1,
}
folder2 := &folder.Folder{
UID: "folder-uid2",
Title: "Folder Title2",
ParentUID: "folder-uid",
Fullpath: "Folder Title2",
OrgID: 1,
}
folder3 := &folder.Folder{
UID: "folder-uid3",
Title: "Folder Title3",
ParentUID: "folder-uid",
Fullpath: "Folder Title3",
OrgID: 3,
}
folderService.SetFolders(map[string]*folder.Folder{
"folder-uid": folder1,
"folder-uid2": folder2,
"folder-uid3": folder3,
})
folderService.ExpectedFolders = []*folder.Folder{
folder1,
folder2,
folder3,
}
// if not one of the two above, return ErrFolderNotFound
folderService.ExpectedError = dashboards.ErrFolderNotFound
store := store.DBstore{
Logger: log,
SQLStore: sqlStore,
@ -2104,23 +2093,6 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
*/
}
parent, err := folderService.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: 1,
UID: "folder-uid",
Title: "Folder Title",
SignedInUser: user,
})
require.NoError(t, err)
_, err = folderService.Create(context.Background(), &folder.CreateFolderCommand{
OrgID: 1,
UID: "folder-uid2",
Title: "Folder Title2",
ParentUID: parent.UID,
SignedInUser: user,
})
require.NoError(t, err)
ruleAuthz := &fakes.FakeRuleService{}
features := featuremgmt.WithFeatures()

View File

@ -27,12 +27,12 @@ import (
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/infra/db"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@ -242,10 +242,10 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
}
sqlStore := db.InitTestDB(t)
folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures())
fakeFolderService := foldertest.NewFakeService()
b := &fakeBus{}
logger := &logtest.Fake{}
store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b)
store := createTestStore(sqlStore, fakeFolderService, logger, cfg.UnifiedAlerting, b)
store.FeatureToggles = featuremgmt.WithFeatures()
gen := models.RuleGen
@ -258,15 +258,38 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
parentFolderUid := uuid.NewString()
parentFolderTitle := "Very Parent Folder"
createFolder(t, store, parentFolderUid, parentFolderTitle, rule1.OrgID, "")
rule1FolderTitle := "folder-" + rule1.Title
rule2FolderTitle := "folder-" + rule2.Title
rule3FolderTitle := "folder-" + rule3.Title
createFolder(t, store, rule1.NamespaceUID, rule1FolderTitle, rule1.OrgID, parentFolderUid)
createFolder(t, store, rule2.NamespaceUID, rule2FolderTitle, rule2.OrgID, "")
createFolder(t, store, rule3.NamespaceUID, rule3FolderTitle, rule3.OrgID, "")
createFolder(t, store, rule2.NamespaceUID, "same UID folder", gen.GenerateRef().OrgID, "") // create a folder with the same UID but in the different org
fakeFolderService.AddFolder(&folder.Folder{
UID: rule1.NamespaceUID,
Title: rule1FolderTitle,
OrgID: rule1.OrgID,
ParentUID: parentFolderUid,
Fullpath: rule1FolderTitle,
})
fakeFolderService.AddFolder(&folder.Folder{
UID: rule2.NamespaceUID,
Title: rule2FolderTitle,
OrgID: rule2.OrgID,
ParentUID: "",
Fullpath: rule2FolderTitle,
})
fakeFolderService.AddFolder(&folder.Folder{
UID: rule3.NamespaceUID,
Title: rule3FolderTitle,
OrgID: rule3.OrgID,
ParentUID: "",
Fullpath: rule3FolderTitle,
})
fakeFolderService.AddFolder(&folder.Folder{
UID: parentFolderUid,
Title: parentFolderTitle,
OrgID: rule1.OrgID,
ParentUID: "",
Fullpath: parentFolderTitle,
})
tc := []struct {
name string
@ -343,7 +366,14 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
}
t.Run("when nested folders are enabled folders should contain full path", func(t *testing.T) {
store.FolderService = setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
fakeFolderService.AddFolder(&folder.Folder{
UID: rule1.NamespaceUID,
Title: rule1FolderTitle,
OrgID: rule1.OrgID,
ParentUID: parentFolderUid,
Fullpath: parentFolderTitle + "/" + rule1FolderTitle,
})
query := &models.GetAlertRulesForSchedulingQuery{
PopulateFolders: true,
}
@ -1597,27 +1627,6 @@ func createRule(t *testing.T, store *DBstore, generator *models.AlertRuleGenerat
return rule
}
func createFolder(t *testing.T, store *DBstore, uid, title string, orgID int64, parentUID string) {
t.Helper()
u := &user.SignedInUser{
UserID: 1,
OrgID: orgID,
OrgRole: org.RoleAdmin,
IsGrafanaAdmin: true,
}
_, err := store.FolderService.Create(context.Background(), &folder.CreateFolderCommand{
UID: uid,
OrgID: orgID,
Title: title,
Description: "",
SignedInUser: u,
ParentUID: parentUID,
})
require.NoError(t, err)
}
func setupFolderService(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles) folder.Service {
tracer := tracing.InitializeTracerForTest()
inProcBus := bus.ProvideBus(tracer)

View File

@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/org"
@ -28,7 +27,7 @@ func TestIntegration_GetUserVisibleNamespaces(t *testing.T) {
sqlStore := db.InitTestDB(t)
cfg := setting.NewCfg()
folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures())
folderService := foldertest.NewFakeService()
b := &fakeBus{}
logger := log.New("test-dbstore")
store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b)
@ -40,18 +39,14 @@ func TestIntegration_GetUserVisibleNamespaces(t *testing.T) {
IsGrafanaAdmin: true,
}
folders := []struct {
uid string
title string
parentUid string
}{
{uid: uuid.NewString(), title: "folder1", parentUid: ""},
{uid: uuid.NewString(), title: "folder2", parentUid: ""},
{uid: uuid.NewString(), title: "nested/folder", parentUid: ""},
folders := []*folder.Folder{
{UID: uuid.NewString(), Title: "folder1", ParentUID: "", OrgID: 1},
{UID: uuid.NewString(), Title: "folder2", ParentUID: "", OrgID: 1},
{UID: uuid.NewString(), Title: "nested/folder", ParentUID: "", OrgID: 1},
}
for _, f := range folders {
createFolder(t, store, f.uid, f.title, 1, f.parentUid)
folderService.AddFolder(f)
}
t.Run("returns all folders", func(t *testing.T) {

View File

@ -24,13 +24,12 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/ngalert"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
@ -83,9 +82,8 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration, opts ...TestEnvOpti
tracer := tracing.InitializeTracerForTest()
bus := bus.ProvideBus(tracer)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
dashboardService, dashboardStore := testutil.SetupDashboardService(tb, sqlStore, folderStore, cfg)
folderService := testutil.SetupFolderService(tb, cfg, sqlStore, dashboardStore, folderStore, bus, options.featureToggles, ac)
folderService := foldertest.NewFakeService()
dashboardService := dashboards.NewFakeDashboardService(tb)
ruleStore, err := store.ProvideDBStore(cfg, options.featureToggles, sqlStore, folderService, &dashboards.FakeDashboardService{}, ac, bus)
require.NoError(tb, err)
ng, err := ngalert.ProvideService(

View File

@ -504,8 +504,10 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe
folderSvc := folderimpl.ProvideService(
fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
require.NoError(t, err)
dashService, err := dashService.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(),
ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, orgService, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()),
kvstore.NewFakeKVStore())
require.NoError(t, err)

View File

@ -10,13 +10,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/dashboards/database"
@ -25,14 +21,11 @@ import (
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search/sort"
"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/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
@ -826,31 +819,42 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
// dashboard store commands that should be called.
dashStore, err := database.ProvideDashboardStore(db, cfg, features, tagimpl.ProvideService(db))
require.NoError(t, err)
fStore := folderimpl.ProvideStore(db)
folderSvc := folderimpl.ProvideService(
fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(db),
nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
// create parent folder
parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: "parent",
OrgID: orgID,
// create in both the folder & dashboard tables
parent, err := fStore.Create(context.Background(), folder.CreateFolderCommand{
Title: "parent",
SignedInUser: usr,
})
require.NoError(t, err)
// create subfolder
subfolder, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: "subfolder",
ParentUID: "parent",
OrgID: orgID,
Title: "subfolder",
UID: "parent",
SignedInUser: usr,
})
require.NoError(t, err)
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,
Dashboard: simplejson.NewFromAny(map[string]any{
"title": "parent",
"uid": parent.UID,
}),
IsFolder: true,
})
require.NoError(t, err)
subfolder, err := fStore.Create(context.Background(), folder.CreateFolderCommand{
Title: "subfolder",
OrgID: orgID,
UID: "subfolder",
ParentUID: parent.UID,
SignedInUser: usr,
})
require.NoError(t, err)
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,
FolderUID: parent.UID,
Dashboard: simplejson.NewFromAny(map[string]any{
"title": "subfolder",
"uid": subfolder.UID,
}),
IsFolder: true,
})
require.NoError(t, err)
// create a root level dashboard
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,

View File

@ -158,11 +158,6 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS
if ss.IsUnifiedAlertingEnabled() {
sb.Write(`(SELECT COUNT(DISTINCT (` + dialect.Quote("rule_group") + `)) FROM ` + dialect.Quote("alert_rule") + `) AS rule_groups,`)
}
// currently not supported when dashboards are in unified storage
if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
sb.Write(`(SELECT SUM(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_total,`, dialect.BooleanValue(false))
sb.Write(`(SELECT MAX(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_max,`, dialect.BooleanValue(false))
}
sb.Write(ss.roleCounterSQL(ctx))

View File

@ -62,7 +62,6 @@ protocol = https
[feature_toggles]
grafanaAPIServerWithExperimentalAPIs = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 4
@ -259,7 +258,6 @@ To enable it, add the following to your `custom.ini` under the `[feature_toggles
[feature_toggles]
; Used by the Grafana instance
unifiedStorageSearchUI = true
kubernetesClientDashboardsFolders = true
; Used by unified storage
unifiedStorageSearch = true
@ -369,7 +367,6 @@ signing_keys_url = http://localhost:3011/api/signing-keys/keys
mode = "on-prem"
[feature_toggles]
kubernetesClientDashboardsFolders = true
kubernetesDashboardsAPI = true
kubernetesFolders = true
unifiedStorage = true
@ -418,7 +415,6 @@ http_port = 3011
http_addr = "127.0.0.2"
[feature_toggles]
kubernetesClientDashboardsFolders = true
kubernetesDashboardsAPI = true
kubernetesFolders = true
unifiedStorageSearchUI = true

View File

@ -19,7 +19,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/dashboardimport"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/plugindashboards"
@ -42,7 +41,6 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
@ -279,7 +277,6 @@ func TestIntegrationDashboardQuota(t *testing.T) {
DisableAnonymous: true,
EnableQuota: true,
DashboardOrgQuota: &dashboardQuota,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
@ -338,7 +335,6 @@ func TestIntegrationUpdatingProvisionionedDashboards(t *testing.T) {
// Setup Grafana and its Database
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
provDashboardsDir := filepath.Join(dir, "conf", "provisioning", "dashboards")
@ -492,7 +488,6 @@ func TestIntegrationCreate(t *testing.T) {
// Setup Grafana and its Database
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
@ -648,7 +643,6 @@ func intPtr(n int) *int {
func TestIntegrationPreserveSchemaVersion(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
@ -739,7 +733,6 @@ func TestIntegrationPreserveSchemaVersion(t *testing.T) {
func TestIntegrationImportDashboardWithLibraryPanels(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
@ -997,7 +990,6 @@ func TestIntegrationDashboardServicePermissions(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{

View File

@ -46,7 +46,7 @@ func TestIntegrationFolderServiceGetFolder(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders, featuremgmt.FlagKubernetesClientDashboardsFolders},
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders},
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, p)
@ -116,7 +116,6 @@ func TestIntegrationUpdateFolder(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableQuota: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
@ -159,7 +158,6 @@ func TestIntegrationCreateFolder(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableQuota: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
@ -208,7 +206,6 @@ func TestIntegrationNestedFoldersOn(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableQuota: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
@ -362,7 +359,7 @@ func TestIntegrationSharedWithMe(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders, featuremgmt.FlagKubernetesClientDashboardsFolders},
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
@ -416,7 +413,6 @@ func TestIntegrationBasicRoles(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
@ -554,7 +550,7 @@ func TestIntegrationFineGrainedPermissions(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders, featuremgmt.FlagKubernetesClientDashboardsFolders},
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)

View File

@ -35,7 +35,6 @@ func TestGetFolders(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)

View File

@ -222,12 +222,7 @@ func TestIntegrationLegacySupport(t *testing.T) {
t.Skip("skipping integration test in short mode")
}
ctx := context.Background()
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
EnableFeatureToggles: []string{
// NOTE: when using this feature toggle, the read is always v0!
// featuremgmt.FlagKubernetesClientDashboardsFolders
},
})
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{})
clientV0 := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,

View File

@ -69,7 +69,6 @@ func TestIntegrationDashboardAPIValidation(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders, // Enable dashboard feature
featuremgmt.FlagUnifiedStorageSearch,
featuremgmt.FlagKubernetesDashboards, // Enable FE-only dashboard feature flag
},
@ -101,7 +100,6 @@ func TestIntegrationDashboardAPIValidation(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders, // Enable dashboard feature
featuremgmt.FlagUnifiedStorageSearch,
},
DisableFeatureToggles: []string{
@ -138,7 +136,6 @@ func TestIntegrationDashboardAPIAuthorization(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders, // Enable dashboard feature
featuremgmt.FlagUnifiedStorageSearch,
},
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
@ -189,7 +186,6 @@ func TestIntegrationDashboardAPI(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders, // Enable dashboard feature
featuremgmt.FlagUnifiedStorageSearch,
featuremgmt.FlagKubernetesDashboards,
},

View File

@ -32,7 +32,6 @@ func TestIntegrationLibraryPanelConnections(t *testing.T) {
DisableAnonymous: true,
EnableFeatureToggles: []string{
"unifiedStorageSearch",
"kubernetesClientDashboardsFolders",
"kubernetesLibraryPanels",
},
})
@ -98,7 +97,6 @@ func TestIntegrationLibraryElementPermissions(t *testing.T) {
DisableAnonymous: true,
EnableFeatureToggles: []string{
"unifiedStorageSearch",
"kubernetesClientDashboardsFolders",
"kubernetesLibraryPanels",
"grafanaAPIServerWithExperimentalAPIs", // needed until we move it to v0beta1 at least (currently v0alpha1)
},
@ -303,7 +301,6 @@ func TestIntegrationLibraryPanelConnectionsWithFolderAccess(t *testing.T) {
EnableFeatureToggles: []string{
"unifiedStorageSearch",
"kubernetesLibraryPanels",
"kubernetesClientDashboardsFolders",
},
})
ctx := createTestContext(t, helper, helper.Org1, dualWriterMode)

View File

@ -51,9 +51,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
EnableFeatureToggles: []string{},
})
t.Run("Check discovery client", func(t *testing.T) {
@ -138,9 +136,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
DualWriterMode: modeDw,
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
EnableFeatureToggles: []string{},
}))
})
@ -155,7 +151,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@ -172,7 +167,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@ -189,7 +183,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@ -206,7 +199,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@ -237,7 +229,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
// We set it to 1 here, so we always get forced pagination based on the response size.
UnifiedStorageMaxPageSizeBytes: 1,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}), mode)
@ -680,7 +671,6 @@ func TestIntegrationFolderCreatePermissions(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@ -788,7 +778,6 @@ func TestIntegrationFolderGetPermissions(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@ -972,7 +961,6 @@ func TestIntegrationFoldersCreateAPIEndpointK8S(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@ -1148,7 +1136,6 @@ func TestIntegrationFoldersGetAPIEndpointK8S(t *testing.T) {
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagUnifiedStorageSearch,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})

View File

@ -28,7 +28,6 @@ func TestIntegrationOpenAPIs(t *testing.T) {
h := NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders, // Will be default on by G12
featuremgmt.FlagQueryService, // Query Library
featuremgmt.FlagProvisioning,
featuremgmt.FlagInvestigationsBackend,

View File

@ -221,7 +221,6 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
AppModeProduction: false, // required for experimental APIs
EnableFeatureToggles: []string{
featuremgmt.FlagProvisioning,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {

View File

@ -383,13 +383,8 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
}
public onRestore = async (version: DecoratedRevisionModel): Promise<boolean> => {
let versionRsp;
if (config.featureToggles.kubernetesClientDashboardsFolders) {
// the id here is the resource version in k8s, use this instead to get the specific version
versionRsp = await historySrv.restoreDashboard(version.uid, version.id);
} else {
versionRsp = await historySrv.restoreDashboard(version.uid, version.version);
}
let versionRsp = await historySrv.restoreDashboard(version.uid, version.id);
if (!Number.isInteger(versionRsp.version)) {
return false;

View File

@ -1,4 +1,3 @@
import { config } from '@grafana/runtime';
import { SceneTimeRange } from '@grafana/scenes';
import { DashboardScene } from '../scene/DashboardScene';
@ -149,10 +148,7 @@ describe('VersionsEditView', () => {
expect(versionsView.versions.find((rev) => rev.version === 1)).toBeUndefined();
});
it('should correctly identify last page when kubernetesClientDashboardsFolders is enabled and continueToken is empty', async () => {
// @ts-ignore
config.featureToggles.kubernetesClientDashboardsFolders = true;
it('should correctly identify last page when continueToken is empty', async () => {
jest.mocked(historySrv.getHistoryList).mockResolvedValueOnce({
continueToken: '',
versions: [
@ -190,10 +186,6 @@ describe('VersionsEditView', () => {
expect(versionsView.versions.length).toBeLessThan(VERSIONS_FETCH_LIMIT);
expect(versionsView.versions.find((rev) => rev.version === 1)).toBeUndefined();
expect(versionsView.continueToken).toBe('');
// reset feature flag
// @ts-ignore
config.featureToggles.kubernetesClientDashboardsFolders = false;
});
});
});

View File

@ -1,7 +1,6 @@
import * as React from 'react';
import { PageLayoutType, dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data';
import { config } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, sceneGraph } from '@grafana/scenes';
import { Spinner, Stack } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
@ -136,15 +135,9 @@ export class VersionsEditView extends SceneObjectBase<VersionsEditViewState> imp
if (!this._dashboard.state.uid) {
return;
}
let lhs, rhs;
if (config.featureToggles.kubernetesClientDashboardsFolders) {
// the id here is the resource version in k8s, use this instead to get the specific version
lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.id);
rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.id);
} else {
lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.version);
rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.version);
}
let lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.id);
let rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.id);
this.setState({
baseInfo,
@ -204,10 +197,10 @@ function VersionsEditorSettingsListView({ model }: SceneComponentProps<VersionsE
const showButtons = model.versions.length > 1;
const hasMore = model.versions.length >= model.limit;
// older versions may have been cleaned up in the db, so also check if the last page is less than the limit, if so, we are at the end
let isLastPage = model.versions.find((rev) => rev.version === 1) || model.versions.length % model.limit !== 0;
if (config.featureToggles.kubernetesClientDashboardsFolders) {
isLastPage = isLastPage || model.continueToken === '';
}
let isLastPage =
model.versions.find((rev) => rev.version === 1) ||
model.versions.length % model.limit !== 0 ||
model.continueToken === '';
const viewModeCompare = (
<>

View File

@ -2,7 +2,6 @@ import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from 'test/test-utils';
import { config } from '@grafana/runtime';
import { historySrv } from 'app/features/dashboard-scene/settings/version-history/HistorySrv';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
@ -110,7 +109,7 @@ describe('VersionSettings', () => {
test('renders buttons if versions >= VERSIONS_FETCH_LIMIT', async () => {
historySrv.getHistoryList = jest.fn().mockResolvedValue({
continueToken: versions.continueToken,
continueToken: 'next-page-token',
versions: versions.versions.slice(0, VERSIONS_FETCH_LIMIT),
});
@ -135,13 +134,13 @@ describe('VersionSettings', () => {
.fn()
.mockImplementationOnce(() =>
Promise.resolve({
continueToken: versions.continueToken,
continueToken: 'next-page-token',
versions: versions.versions.slice(0, VERSIONS_FETCH_LIMIT),
})
)
.mockImplementationOnce(() =>
Promise.resolve({
continueToken: versions.continueToken,
continueToken: '',
versions: versions.versions.slice(VERSIONS_FETCH_LIMIT),
})
);
@ -184,8 +183,7 @@ describe('VersionSettings', () => {
expect(screen.getByRole('button', { name: /compare versions/i })).toBeInTheDocument();
});
test('does not show more button when kubernetesClientDashboardsFolders is enabled and continueToken is empty', async () => {
config.featureToggles.kubernetesClientDashboardsFolders = true;
test('does not show more button when continueToken is empty', async () => {
historySrv.getHistoryList = jest.fn().mockResolvedValueOnce({
continueToken: '',
versions: versions.versions.slice(0, VERSIONS_FETCH_LIMIT - 1),
@ -197,8 +195,6 @@ describe('VersionSettings', () => {
expect(screen.queryByRole('button', { name: /show more versions/i })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: /compare versions/i })).toBeInTheDocument();
config.featureToggles.kubernetesClientDashboardsFolders = false;
});
test('selecting two versions and clicking compare button should render compare view', async () => {

View File

@ -1,7 +1,6 @@
import { PureComponent } from 'react';
import * as React from 'react';
import { config } from '@grafana/runtime';
import { Spinner, HorizontalGroup } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { historySrv, RevisionsModel } from 'app/features/dashboard-scene/settings/version-history/HistorySrv';
@ -90,15 +89,9 @@ export class VersionsSettings extends PureComponent<Props, State> {
isLoading: true,
});
let lhs, rhs;
if (config.featureToggles.kubernetesClientDashboardsFolders) {
// the id here is the resource version in k8s, use this instead to get the specific version
lhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, baseInfo.id);
rhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, newInfo.id);
} else {
lhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, baseInfo.version);
rhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, newInfo.version);
}
let lhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, baseInfo.id);
let rhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, newInfo.id);
this.setState({
baseInfo,
@ -122,15 +115,12 @@ export class VersionsSettings extends PureComponent<Props, State> {
}));
isLastPage() {
if (config.featureToggles.kubernetesClientDashboardsFolders) {
return (
this.state.versions.find((rev) => rev.version === 1) ||
this.state.versions.length % this.limit !== 0 ||
this.continueToken === ''
);
}
return this.state.versions.find((rev) => rev.version === 1) || this.state.versions.length % this.limit !== 0;
}
onCheck = (ev: React.FormEvent<HTMLInputElement>, versionId: number) => {
this.setState({

View File

@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { useAsyncFn } from 'react-use';
import { locationUtil } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import { locationService } from '@grafana/runtime';
import { useAppNotification } from 'app/core/copy/appNotification';
import { historySrv } from 'app/features/dashboard-scene/settings/version-history/HistorySrv';
import { useSelector } from 'app/types/store';
@ -18,11 +18,7 @@ const restoreDashboard = async (version: number, dashboard: DashboardModel) => {
export const useDashboardRestore = (id: number, version: number) => {
const dashboard = useSelector((state) => state.dashboard.getModel());
const [state, onRestoreDashboard] = useAsyncFn(
async () =>
await restoreDashboard(config.featureToggles.kubernetesClientDashboardsFolders ? id : version, dashboard!),
[]
);
const [state, onRestoreDashboard] = useAsyncFn(async () => await restoreDashboard(id, dashboard!), []);
const notifyApp = useAppNotification();
useEffect(() => {

View File

@ -1,11 +1,7 @@
import { FeatureToggles } from '@grafana/data';
import { config } from '@grafana/runtime';
export const requiredFeatureToggles: Array<keyof FeatureToggles> = [
'provisioning',
'kubernetesDashboards',
'kubernetesClientDashboardsFolders',
];
export const requiredFeatureToggles: Array<keyof FeatureToggles> = ['provisioning', 'kubernetesDashboards'];
/**
* Checks if all required feature toggles are enabled