K8s: Move to one feature toggle for folders and dashboards (#100911)

This commit is contained in:
Stephanie Hingtgen
2025-02-19 01:11:26 +02:00
committed by GitHub
parent 9a0e874e9b
commit 3e6f40c873
27 changed files with 162 additions and 233 deletions
@@ -168,9 +168,8 @@ Experimental features might be changed or removed without prior notice.
| `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) |
| `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint |
| `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards |
| `kubernetesCliDashboards` | Use the k8s client to retrieve dashboards internally |
| `kubernetesRestore` | Allow restoring objects in k8s |
| `kubernetesFoldersServiceV2` | Use the Folders Service V2, and route Folder Service requests to k8s |
| `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s |
| `datasourceQueryTypes` | Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) |
| `queryService` | Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query |
| `queryServiceRewrite` | Rewrite requests targeting /ds/query to the query service |
@@ -108,9 +108,8 @@ export interface FeatureToggles {
kubernetesPlaylists?: boolean;
kubernetesSnapshots?: boolean;
kubernetesDashboards?: boolean;
kubernetesCliDashboards?: boolean;
kubernetesRestore?: boolean;
kubernetesFoldersServiceV2?: boolean;
kubernetesClientDashboardsFolders?: boolean;
datasourceQueryTypes?: boolean;
queryService?: boolean;
queryServiceRewrite?: boolean;
+1 -1
View File
@@ -192,7 +192,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.FlagKubernetesFoldersServiceV2) {
if dash.FolderUID != "" && hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
queryResult, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: c.SignedInUser.GetOrgID(),
UID: &dash.FolderUID,
+1 -1
View File
@@ -625,7 +625,7 @@ func TestGetFolderLegacyAndUnifiedStorage(t *testing.T) {
featuresArr := []any{featuremgmt.FlagNestedFolders}
if tc.unifiedStorageEnabled {
featuresArr = append(featuresArr, featuremgmt.FlagKubernetesFoldersServiceV2)
featuresArr = append(featuresArr, featuremgmt.FlagKubernetesClientDashboardsFolders)
}
server := SetupAPITestServer(t, func(hs *HTTPServer) {
+1 -1
View File
@@ -66,7 +66,7 @@ func RegisterAPIService(cfg *setting.Cfg,
unified resource.ResourceClient,
) *FolderAPIBuilder {
if !featuremgmt.AnyEnabled(features,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
featuremgmt.FlagProvisioning) {
return nil // skip registration unless opting into Kubernetes folders or unless we want to customize registration when testing
@@ -110,7 +110,7 @@ func ProvideFolderPermissions(
// if the folder is not found, this may be on the create path,
// where the write path to legacy will then go through the read
// path and try to read from both legacy & unified before it exists on both
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) && errors.Is(err, dashboards.ErrFolderNotFound) {
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) && errors.Is(err, dashboards.ErrFolderNotFound) {
_, err = folderService.GetLegacy(ctx, &folder.GetFolderQuery{
UID: &resourceID,
OrgID: orgID,
@@ -152,7 +152,7 @@ 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.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
u := &quota.Map{}
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
@@ -188,7 +188,7 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc
}
func (dr *DashboardServiceImpl) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
resp, err := dr.k8sclient.GetStats(ctx, orgID)
if err != nil {
return 0, err
@@ -226,7 +226,7 @@ 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.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
return nil, err
@@ -267,7 +267,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context,
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*dashboards.DashboardProvisioning, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// if dashboard id is 0, it is a new dashboard
if dashboardID == 0 {
return nil, nil
@@ -301,7 +301,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx con
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioning, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
if dashboardUID == "" {
return nil, nil
}
@@ -368,7 +368,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
}
// Validate folder
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: dash.OrgID,
UID: &dash.FolderUID,
@@ -555,7 +555,7 @@ func (dr *DashboardServiceImpl) ValidateDashboardBeforeSave(ctx context.Context,
}
func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *dashboards.DeleteOrphanedProvisionedDashboardsCommand) error {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// check each org for orphaned provisioned dashboards
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
@@ -673,7 +673,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
}
var dash *dashboards.Dashboard
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// save the dashboard but then do NOT return
// we want to save the provisioning data to the dashboard_provisioning table still
// to ensure we can safely rollback to mode2 if needed
@@ -748,7 +748,7 @@ 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.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.saveDashboardThroughK8s(ctx, cmd, cmd.OrgID)
}
@@ -756,7 +756,7 @@ func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboar
}
func (dr *DashboardServiceImpl) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.getDashboardThroughK8s(ctx, &dashboards.GetDashboardQuery{OrgID: orgID, UID: uid, IncludeDeleted: true})
}
@@ -815,7 +815,7 @@ func (dr *DashboardServiceImpl) SoftDeleteDashboard(ctx context.Context, orgID i
return fmt.Errorf("feature flag %s is not enabled", featuremgmt.FlagDashboardRestore)
}
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// deletes in unistore are soft deletes, so we can just delete in the same way
return dr.deleteDashboardThroughK8s(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgID, UID: dashboardUID}, true)
}
@@ -836,7 +836,7 @@ 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.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.deleteAllDashboardThroughK8s(ctx, orgId)
}
@@ -859,7 +859,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId
cmd := &dashboards.DeleteDashboardCommand{OrgID: orgId, ID: dashboardId, UID: dashboardUID}
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
err := dr.deleteDashboardThroughK8s(ctx, cmd, validateProvisionedDashboard)
if err != nil {
return err
@@ -924,7 +924,7 @@ 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.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
return err
@@ -957,7 +957,7 @@ func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashbo
}
func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, query *dashboards.GetDashboardsByPluginIDQuery) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
OrgId: query.OrgID,
ProvisionedRepo: dashboard.PluginIDRepoName,
@@ -1055,7 +1055,7 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context,
}
func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.getDashboardThroughK8s(ctx, query)
}
@@ -1063,7 +1063,7 @@ func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboa
}
func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *dashboards.GetDashboardRefByIDQuery) (*dashboards.DashboardRef, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
requester, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
@@ -1089,7 +1089,7 @@ func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *
}
func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *dashboards.GetDashboardsQuery) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
if query.OrgID == 0 {
requester, err := identity.GetRequester(ctx)
if err != nil {
@@ -1237,7 +1237,7 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
}(time.Now())
}
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
if query.OrgId == 0 {
requester, err := identity.GetRequester(ctx)
if err != nil {
@@ -1319,7 +1319,7 @@ func (dr *DashboardServiceImpl) SearchDashboards(ctx context.Context, query *das
}
func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
requester, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
@@ -1331,7 +1331,7 @@ func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashbo
}
func (dr *DashboardServiceImpl) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.listDashboardsThroughK8s(ctx, orgID)
}
@@ -1405,7 +1405,7 @@ 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.FlagKubernetesCliDashboards) {
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
res, err := dr.k8sclient.Search(ctx, query.OrgID, &resource.ResourceSearchRequest{
Facet: map[string]*resource.ResourceSearchRequest_Facet{
"tags": {
@@ -1437,7 +1437,7 @@ func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *das
}
func (dr DashboardServiceImpl) CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, u identity.Requester) (int64, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
OrgId: orgID,
FolderUIDs: folderUIDs,
@@ -265,7 +265,7 @@ func TestDashboardService(t *testing.T) {
func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *client.MockK8sHandler) {
mockCli := new(client.MockK8sHandler)
service.k8sclient = mockCli
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
ctx := context.Background()
userCtx := &user.SignedInUser{UserID: 1, OrgID: 1}
@@ -1096,7 +1096,13 @@ func TestSaveProvisionedDashboard(t *testing.T) {
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
log: log.NewNopLogger(),
folderService: &foldertest.FakeService{
ExpectedFolder: &folder.Folder{
ID: 0,
UID: "general",
},
},
log: log.NewNopLogger(),
}
origNewDashboardGuardian := guardian.New
@@ -1160,6 +1166,9 @@ func TestSaveDashboard(t *testing.T) {
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
folderService: &foldertest.FakeService{
ExpectedFolder: &folder.Folder{},
},
}
origNewDashboardGuardian := guardian.New
@@ -86,7 +86,7 @@ func (s *Service) Get(ctx context.Context, query *dashver.GetDashboardVersionQue
query.DashboardID = id
}
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
version, err := s.getHistoryThroughK8s(ctx, query.OrgID, query.DashboardUID, query.Version)
if err != nil {
return nil, err
@@ -157,7 +157,7 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
query.Limit = 1000
}
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
versions, err := s.listHistoryThroughK8s(
ctx,
query.OrgID,
@@ -44,7 +44,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.FlagKubernetesCliDashboards)
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil)
mockCli.On("GetUserFromMeta", mock.Anything, "user:1").Return(&user.User{ID: 1}, nil)
@@ -232,7 +232,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.FlagKubernetesCliDashboards)
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
dashboardService.On("GetDashboardUIDByID", mock.Anything,
mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
+3 -9
View File
@@ -680,12 +680,6 @@ var (
Owner: grafanaAppPlatformSquad,
FrontendOnly: true,
},
{
Name: "kubernetesCliDashboards",
Description: "Use the k8s client to retrieve dashboards internally",
Stage: FeatureStageExperimental,
Owner: grafanaAppPlatformSquad,
},
{
Name: "kubernetesRestore",
Description: "Allow restoring objects in k8s",
@@ -693,10 +687,10 @@ var (
Owner: grafanaAppPlatformSquad,
},
{
Name: "kubernetesFoldersServiceV2",
Description: "Use the Folders Service V2, and route Folder Service requests to k8s",
Name: "kubernetesClientDashboardsFolders",
Description: "Route the folder and dashboard service requests to k8s",
Stage: FeatureStageExperimental,
Owner: grafanaSearchAndStorageSquad,
Owner: grafanaAppPlatformSquad,
},
{
Name: "datasourceQueryTypes",
+1 -3
View File
@@ -360,7 +360,6 @@ appPlatformAccessTokens,2024-09-05T16:18:44Z,2024-10-14T10:47:18Z,d5ebaa0ef92ede
appSidecar,2024-09-09T12:45:05Z,,5e2ac24890906e5070323d87730dd78a4f885963,Andrej Ocenas
vizActions,2024-09-09T14:11:55Z,,af48d3db1eb2d8681843f5997e50fea5e5ea3096,Adela Almasan
groupAttributeSync,2024-09-09T15:29:43Z,,6ded6a8872204a818b3795dc733cc5fe5db066a0,Aaron Godin
kubernetesFolders,2024-09-10T09:22:08Z,,b12a29a1dac8b9aec4a99be08e1665939cb27dc5,Arati R.
alertingFilterV2,2024-09-11T11:29:26Z,,90ee52e8d9c14237f8a57b622c0def7512e657cd,Gilles De Mey
improvedExternalSessionHandling,2024-09-17T10:54:39Z,,41cd0f51800d4849345fc0980ca4173967fc8e9e,Misi
datasourceAPIServers,2024-09-19T08:28:27Z,,f21a5987a22bcdb596d6a258d2960e4151348b63,Ryan McKinley
@@ -407,7 +406,6 @@ feedbackButton,2024-12-02T17:08:15Z,,8a1b89a5ebb847f6b29e92dcea796f00c30431d6,Mi
elasticsearchCrossClusterSearch,2024-12-12T22:20:04Z,,b3a12f486eba69e20dd7ff3a3d4dd065ede7a99f,Isabella Siu
unifiedHistory,2024-12-13T10:41:18Z,,aac62c89dae1092836a91d1b6ae6bd7127fe676a,Laura Fernández
lokiLabelNamesQueryApi,2024-12-13T14:31:41Z,,5ac7443fcec0db412d3333044a82c2c26b5aece7,Sven Grossmann
kubernetesCliDashboards,2024-12-13T22:55:43Z,,8f6e9f8ed0a5024a510cc337c9f1e6972bfb23d4,Stephanie Hingtgen
useV2DashboardsAPI,2024-12-17T21:17:09Z,,070f0e4457c5967102ef157197073dc2662f6fb8,Dominik Prokop
investigationsBackend,2024-12-18T08:31:03Z,,f46c07aba7b6faccd2ecafc83051d1410cacc867,Jackson Coelho
unifiedStorageSearchSprinkles,2024-12-18T17:00:54Z,,4837585cab0fd84184a8c6f5d6891f442a2b95f1,owensmallwood
@@ -421,9 +419,9 @@ improvedExternalSessionHandlingSAML,2025-01-09T17:02:49Z,,c52ec21c75ab72c2f7d282
teamHttpHeadersMimir,2025-01-13T10:42:47Z,,04acbcdef23f673bd6bbfdbbece29c9769ce155a,Eric Leijonmarck
ABTestFeatureToggleA,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs
ABTestFeatureToggleB,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs
kubernetesFoldersServiceV2,2025-01-13T21:15:35Z,,766d645d827f5e6e0872ae30e5fe23226ae85785,maicon
queryLibraryDashboards,2025-01-14T11:01:15Z,,740cd22fe51a3543c182857f31fc97fd42263306,Ashley Harrison
elasticsearchImprovedParsing,2025-01-15T17:05:54Z,,bab55a4cb84f2ba57838f96a492ab9aa7f307957,Adam Yeats
grafanaAdvisor,2025-01-20T10:08:00Z,,c1364d6be6f552203ba786f17a89664304b89247,Andres Martinez Gotor
datasourceConnectionsTab,2025-01-21T17:39:48Z,,97d8f68b705f9949493079d1833abfe80e7b48f3,Syerikjan Kh
unifiedStorageSearchPermissionFiltering,2025-01-22T11:38:37Z,,dd483fc17fa4a2931848e3574cfc31ea6f6530d9,owensmallwood
kubernetesClientDashboardsFolders,2025-02-18T21:15:35Z,,766d645d827f5e6e0872ae30e5fe23226ae85785,Stephanie Hingtgen
1 #name created deleted hash author
360 appSidecar 2024-09-09T12:45:05Z 5e2ac24890906e5070323d87730dd78a4f885963 Andrej Ocenas
361 vizActions 2024-09-09T14:11:55Z af48d3db1eb2d8681843f5997e50fea5e5ea3096 Adela Almasan
362 groupAttributeSync 2024-09-09T15:29:43Z 6ded6a8872204a818b3795dc733cc5fe5db066a0 Aaron Godin
kubernetesFolders 2024-09-10T09:22:08Z b12a29a1dac8b9aec4a99be08e1665939cb27dc5 Arati R.
363 alertingFilterV2 2024-09-11T11:29:26Z 90ee52e8d9c14237f8a57b622c0def7512e657cd Gilles De Mey
364 improvedExternalSessionHandling 2024-09-17T10:54:39Z 41cd0f51800d4849345fc0980ca4173967fc8e9e Misi
365 datasourceAPIServers 2024-09-19T08:28:27Z f21a5987a22bcdb596d6a258d2960e4151348b63 Ryan McKinley
406 elasticsearchCrossClusterSearch 2024-12-12T22:20:04Z b3a12f486eba69e20dd7ff3a3d4dd065ede7a99f Isabella Siu
407 unifiedHistory 2024-12-13T10:41:18Z aac62c89dae1092836a91d1b6ae6bd7127fe676a Laura Fernández
408 lokiLabelNamesQueryApi 2024-12-13T14:31:41Z 5ac7443fcec0db412d3333044a82c2c26b5aece7 Sven Grossmann
kubernetesCliDashboards 2024-12-13T22:55:43Z 8f6e9f8ed0a5024a510cc337c9f1e6972bfb23d4 Stephanie Hingtgen
409 useV2DashboardsAPI 2024-12-17T21:17:09Z 070f0e4457c5967102ef157197073dc2662f6fb8 Dominik Prokop
410 investigationsBackend 2024-12-18T08:31:03Z f46c07aba7b6faccd2ecafc83051d1410cacc867 Jackson Coelho
411 unifiedStorageSearchSprinkles 2024-12-18T17:00:54Z 4837585cab0fd84184a8c6f5d6891f442a2b95f1 owensmallwood
419 teamHttpHeadersMimir 2025-01-13T10:42:47Z 04acbcdef23f673bd6bbfdbbece29c9769ce155a Eric Leijonmarck
420 ABTestFeatureToggleA 2025-01-13T21:13:13Z 009d7f42b3d09b3a6be1f00f07314e2b25af7ebc Nathan Marrs
421 ABTestFeatureToggleB 2025-01-13T21:13:13Z 009d7f42b3d09b3a6be1f00f07314e2b25af7ebc Nathan Marrs
kubernetesFoldersServiceV2 2025-01-13T21:15:35Z 766d645d827f5e6e0872ae30e5fe23226ae85785 maicon
422 queryLibraryDashboards 2025-01-14T11:01:15Z 740cd22fe51a3543c182857f31fc97fd42263306 Ashley Harrison
423 elasticsearchImprovedParsing 2025-01-15T17:05:54Z bab55a4cb84f2ba57838f96a492ab9aa7f307957 Adam Yeats
424 grafanaAdvisor 2025-01-20T10:08:00Z c1364d6be6f552203ba786f17a89664304b89247 Andres Martinez Gotor
425 datasourceConnectionsTab 2025-01-21T17:39:48Z 97d8f68b705f9949493079d1833abfe80e7b48f3 Syerikjan Kh
426 unifiedStorageSearchPermissionFiltering 2025-01-22T11:38:37Z dd483fc17fa4a2931848e3574cfc31ea6f6530d9 owensmallwood
427 kubernetesClientDashboardsFolders 2025-02-18T21:15:35Z 766d645d827f5e6e0872ae30e5fe23226ae85785 Stephanie Hingtgen
+1 -2
View File
@@ -89,9 +89,8 @@ formatString,GA,@grafana/dataviz-squad,false,false,true
kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false
kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true
kubernetesCliDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,false
kubernetesRestore,experimental,@grafana/grafana-app-platform-squad,false,false,false
kubernetesFoldersServiceV2,experimental,@grafana/search-and-storage,false,false,false
kubernetesClientDashboardsFolders,experimental,@grafana/grafana-app-platform-squad,false,false,false
datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false
queryService,experimental,@grafana/grafana-app-platform-squad,false,true,false
queryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,false,true,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
89 kubernetesPlaylists GA @grafana/grafana-app-platform-squad false true false
90 kubernetesSnapshots experimental @grafana/grafana-app-platform-squad false true false
91 kubernetesDashboards experimental @grafana/grafana-app-platform-squad false false true
kubernetesCliDashboards experimental @grafana/grafana-app-platform-squad false false false
92 kubernetesRestore experimental @grafana/grafana-app-platform-squad false false false
93 kubernetesFoldersServiceV2 kubernetesClientDashboardsFolders experimental @grafana/search-and-storage @grafana/grafana-app-platform-squad false false false
94 datasourceQueryTypes experimental @grafana/grafana-app-platform-squad false true false
95 queryService experimental @grafana/grafana-app-platform-squad false true false
96 queryServiceRewrite experimental @grafana/grafana-app-platform-squad false true false
+3 -7
View File
@@ -367,17 +367,13 @@ const (
// Use the kubernetes API in the frontend for dashboards
FlagKubernetesDashboards = "kubernetesDashboards"
// FlagKubernetesCliDashboards
// Use the k8s client to retrieve dashboards internally
FlagKubernetesCliDashboards = "kubernetesCliDashboards"
// FlagKubernetesRestore
// Allow restoring objects in k8s
FlagKubernetesRestore = "kubernetesRestore"
// FlagKubernetesFoldersServiceV2
// Use the Folders Service V2, and route Folder Service requests to k8s
FlagKubernetesFoldersServiceV2 = "kubernetesFoldersServiceV2"
// FlagKubernetesClientDashboardsFolders
// Route the folder and dashboard service requests to k8s
FlagKubernetesClientDashboardsFolders = "kubernetesClientDashboardsFolders"
// FlagDatasourceQueryTypes
// Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus)
+7 -32
View File
@@ -2227,12 +2227,15 @@
},
{
"metadata": {
"name": "kubernetesCliDashboards",
"resourceVersion": "1733520389522",
"creationTimestamp": "2024-12-13T22:55:43Z"
"name": "kubernetesClientDashboardsFolders",
"resourceVersion": "1739913583714",
"creationTimestamp": "2025-02-18T21:15:35Z",
"annotations": {
"grafana.app/updatedTimestamp": "2025-02-18 21:19:43.714638 +0000 UTC"
}
},
"spec": {
"description": "Use the k8s client to retrieve dashboards internally",
"description": "Route the folder and dashboard service requests to k8s",
"stage": "experimental",
"codeowner": "@grafana/grafana-app-platform-squad"
}
@@ -2264,34 +2267,6 @@
"hideFromAdminPage": true
}
},
{
"metadata": {
"name": "kubernetesFolders",
"resourceVersion": "1725863636605",
"creationTimestamp": "2024-09-10T09:22:08Z",
"deletionTimestamp": "2025-01-22T20:49:15Z",
"annotations": {
"grafana.app/updatedTimestamp": "2024-09-09 06:33:56.605329 +0000 UTC"
}
},
"spec": {
"description": "Use the kubernetes API in the frontend for folders, and route /api/folders requests to k8s",
"stage": "experimental",
"codeowner": "@grafana/search-and-storage"
}
},
{
"metadata": {
"name": "kubernetesFoldersServiceV2",
"resourceVersion": "1735336477446",
"creationTimestamp": "2025-01-13T21:15:35Z"
},
"spec": {
"description": "Use the Folders Service V2, and route Folder Service requests to k8s",
"stage": "experimental",
"codeowner": "@grafana/search-and-storage"
}
},
{
"metadata": {
"name": "kubernetesPlaylists",
+12 -12
View File
@@ -111,7 +111,7 @@ func ProvideService(
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
k8sHandler := client.NewK8sHandler(
cfg,
request.GetNamespaceMapper(cfg),
@@ -129,7 +129,7 @@ func ProvideService(
srv.k8sclient = k8sHandler
}
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashHandler := client.NewK8sHandler(
cfg,
request.GetNamespaceMapper(cfg),
@@ -193,7 +193,7 @@ func (s *Service) DBMigration(db db.DB) {
}
func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery) (model.HitList, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
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)
@@ -204,7 +204,7 @@ func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery
}
func (s *Service) GetFolders(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getFoldersFromApiServer(ctx, q)
}
return s.GetFoldersLegacy(ctx, q)
@@ -263,7 +263,7 @@ func (s *Service) GetFoldersLegacy(ctx context.Context, q folder.GetFoldersQuery
}
func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getFromApiServer(ctx, q)
}
return s.GetLegacy(ctx, q)
@@ -404,7 +404,7 @@ func (s *Service) setFullpath(ctx context.Context, f *folder.Folder, user identi
}
func (s *Service) GetChildren(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getChildrenFromApiServer(ctx, q)
}
return s.GetChildrenLegacy(ctx, q)
@@ -674,7 +674,7 @@ func (s *Service) deduplicateAvailableFolders(ctx context.Context, folders []*fo
}
func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getParentsFromApiServer(ctx, q)
}
return s.GetParentsLegacy(ctx, q)
@@ -707,7 +707,7 @@ func (s *Service) getFolderByTitle(ctx context.Context, orgID int64, title strin
}
func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.createOnApiServer(ctx, cmd)
}
return s.CreateLegacy(ctx, cmd)
@@ -826,7 +826,7 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm
}
func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.updateOnApiServer(ctx, cmd)
}
return s.UpdateLegacy(ctx, cmd)
@@ -965,7 +965,7 @@ func prepareForUpdate(dashFolder *dashboards.Dashboard, orgId int64, userId int6
}
func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.deleteFromApiServer(ctx, cmd)
}
return s.DeleteLegacy(ctx, cmd)
@@ -1088,7 +1088,7 @@ func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderComm
}
func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.moveOnApiServer(ctx, cmd)
}
return s.MoveLegacy(ctx, cmd)
@@ -1315,7 +1315,7 @@ func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFold
}
func (s *Service) GetDescendantCounts(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getDescendantCountsFromApiServer(ctx, q)
}
@@ -26,7 +26,6 @@ import (
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/search"
@@ -659,71 +658,43 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
return folder.ErrFolderNotEmpty.Errorf("folder contains %d alert rules", alertRulesInFolder)
}
// if dashboard restore is on we don't delete public dashboards, the hard delete will take care of it later
if !s.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
// We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards
var dashboardUIDs []string
// we cannot use the dashboard service directly due to circular dependencies,
// so either use the search client if the feature is enabled or use the dashboard store
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
Fields: []*resource.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: folders,
},
},
// We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards -
// we cannot use the dashboard service directly due to circular dependencies, so use the search client to get the dashboards
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
Fields: []*resource.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: folders,
},
Limit: 100000}
},
},
Limit: 100000}
res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
hits, err := dashboardsearch.ParseResults(res, 0)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
dashboardUIDs = make([]string, len(hits.Hits))
for i, dashboard := range hits.Hits {
dashboardUIDs[i] = dashboard.Name
err = s.dashboardK8sClient.Delete(ctx, dashboard.Name, cmd.OrgID, metav1.DeleteOptions{})
if err != nil {
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
}
}
} else {
dashes, err := s.dashboardStore.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{
SignedInUser: cmd.SignedInUser,
FolderUIDs: folders,
OrgId: cmd.OrgID,
Type: searchstore.TypeDashboard,
})
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
dashboardUIDs = make([]string, len(dashes))
for i, dashboard := range dashes {
dashboardUIDs[i] = dashboard.UID
err = s.dashboardStore.DeleteDashboard(ctx, &dashboards.DeleteDashboardCommand{
UID: dashboard.UID,
OrgID: cmd.OrgID,
})
if err != nil {
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
}
}
}
// Delete all public dashboards in the folders
err = s.publicDashboardService.DeleteByDashboardUIDs(ctx, cmd.OrgID, dashboardUIDs)
hits, err := dashboardsearch.ParseResults(res, 0)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
dashboardUIDs := make([]string, len(hits.Hits))
for i, dashboard := range hits.Hits {
dashboardUIDs[i] = dashboard.Name
err = s.dashboardK8sClient.Delete(ctx, dashboard.Name, cmd.OrgID, metav1.DeleteOptions{})
if err != nil {
return folder.ErrInternal.Errorf("failed to delete public dashboards: %w", err)
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
}
}
// Delete all public dashboards in the folders
err = s.publicDashboardService.DeleteByDashboardUIDs(ctx, cmd.OrgID, dashboardUIDs)
if err != nil {
return folder.ErrInternal.Errorf("failed to delete public dashboards: %w", err)
}
}
err = s.unifiedStore.Delete(ctx, folders, cmd.OrgID)
@@ -179,7 +179,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
}
featuresArr := []any{
featuremgmt.FlagKubernetesFoldersServiceV2}
featuremgmt.FlagKubernetesClientDashboardsFolders}
features := featuremgmt.WithFeatures(featuresArr...)
dashboardStore := dashboards.NewFakeDashboardStore(t)
@@ -206,6 +206,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
publicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
fakeK8sClient := new(client.MockK8sHandler)
folderService := &Service{
log: slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"),
unifiedStore: unifiedStore,
@@ -216,7 +217,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
metrics: newFoldersMetrics(nil),
tracer: tracing.InitializeTracerForTest(),
k8sclient: k8sCli,
dashboardStore: dashboardStore,
dashboardK8sClient: fakeK8sClient,
publicDashboardService: publicDashboardService,
}
@@ -348,7 +349,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When deleting folder by uid should not return access denied error - ForceDeleteRules false", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
publicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@@ -362,6 +363,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
t.Run("When deleting folder by uid, expectedForceDeleteRules as false, and dashboard Restore turned on should not return access denied error", func(t *testing.T) {
folderService.features = featuremgmt.WithFeatures(append(featuresArr, featuremgmt.FlagDashboardRestore)...)
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
expectedForceDeleteRules := false
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@@ -375,6 +377,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
t.Run("When deleting folder by uid, expectedForceDeleteRules as true, and dashboard Restore turned on should not return access denied error", func(t *testing.T) {
folderService.features = featuremgmt.WithFeatures(append(featuresArr, featuremgmt.FlagDashboardRestore)...)
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
expectedForceDeleteRules := true
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@@ -526,7 +529,7 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
}
service := Service{
k8sclient: fakeK8sClient,
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
unifiedStore: folderStore,
}
user := &user.SignedInUser{OrgID: 1}
@@ -779,7 +782,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
dashboardStore: dashboardStore,
publicDashboardService: publicDashboardFakeService,
registry: make(map[string]folder.RegistryService),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
}
user := &user.SignedInUser{OrgID: 1}
ctx := identity.WithRequester(context.Background(), user)
@@ -798,52 +801,22 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
require.NoError(t, service.RegisterService(alertingStore))
t.Run("Should delete folder", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{}).Return(nil).Once()
dashboardK8sclient.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
UID: "uid",
UID: "uid1",
OrgID: 1,
SignedInUser: user,
})
require.NoError(t, err)
})
t.Run("Should delete dashboards and public dashboards within the folder", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
{
UID: "test",
OrgID: 1,
},
{
UID: "test2",
OrgID: 1,
},
}, nil).Once()
dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{
UID: "test",
OrgID: 1,
}).Return(nil).Once()
dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{
UID: "test2",
OrgID: 1,
}).Return(nil).Once()
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once()
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
UID: "uid",
OrgID: 1,
SignedInUser: user,
})
require.NoError(t, err)
dashboardStore.AssertExpectations(t)
dashboardK8sclient.AssertExpectations(t)
publicDashboardFakeService.AssertExpectations(t)
})
// enable k8s ff for dashboards, retest
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2, featuremgmt.FlagKubernetesCliDashboards)
t.Run("Should delete dashboards and public dashboards within the folder through k8s if the ff is enabled", func(t *testing.T) {
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"uid1"}).Return(nil).Once()
dashboardK8sclient.On("Delete", mock.Anything, "uid1", int64(1), mock.Anything).Return(nil).Once()
t.Run("Should delete folders, dashboards, and public dashboards within the folder", func(t *testing.T) {
fakeFolderStore.ExpectedFolders = []*folder.Folder{{UID: "uid2", ID: 2}}
dashboardK8sclient.On("Delete", mock.Anything, "test", int64(1), mock.Anything).Return(nil).Once()
dashboardK8sclient.On("Delete", mock.Anything, "test2", int64(1), mock.Anything).Return(nil).Once()
dashboardK8sclient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
@@ -851,7 +824,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: []string{"uid1"},
Values: []string{"uid", "uid2"},
},
},
},
@@ -870,11 +843,21 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "uid1",
Resource: "folder",
Name: "test",
Resource: "dashboard",
},
Cells: [][]byte{
[]byte("folder1"),
[]byte("uid"),
[]byte(""),
},
},
{
Key: &resource.ResourceKey{
Name: "test2",
Resource: "dashboard",
},
Cells: [][]byte{
[]byte("uid2"),
[]byte(""),
},
},
@@ -882,13 +865,14 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
},
TotalHits: 1,
}, nil).Once()
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once()
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
UID: "uid1",
UID: "uid",
OrgID: 1,
SignedInUser: user,
})
require.NoError(t, err)
dashboardStore.AssertExpectations(t)
publicDashboardFakeService.AssertExpectations(t)
dashboardK8sclient.AssertExpectations(t)
})
}
+1 -1
View File
@@ -165,7 +165,7 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS
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.FlagKubernetesCliDashboards) {
if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
sb.Write(`(SELECT SUM(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_total,`, dialect.BooleanStr(false))
sb.Write(`(SELECT MAX(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_max,`, dialect.BooleanStr(false))
}
+7 -2
View File
@@ -61,9 +61,14 @@ target = all
protocol = https
[feature_toggles]
; store folders in k8s
kubernetesFolders = true
grafanaAPIServerWithExperimentalAPIs = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 4
[unified_storage.dashboards.dashboard.grafana.app]
dualWriterMode = 4
[grafana-apiserver]
; use unified storage for k8s apiserver
+11 -11
View File
@@ -49,7 +49,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@@ -123,7 +123,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
}))
})
@@ -139,7 +139,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
}))
})
@@ -155,7 +155,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@@ -172,7 +172,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@@ -189,7 +189,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@@ -206,7 +206,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@@ -577,7 +577,7 @@ func TestIntegrationFolderCreatePermissions(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@@ -679,7 +679,7 @@ func TestIntegrationFolderGetPermissions(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@@ -856,7 +856,7 @@ func TestFoldersCreateAPIEndpointK8S(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@@ -1026,7 +1026,7 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
+2 -2
View File
@@ -28,8 +28,8 @@ func TestIntegrationOpenAPIs(t *testing.T) {
h := NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2, // Will be default on by G12
featuremgmt.FlagQueryService, // Query Library
featuremgmt.FlagKubernetesClientDashboardsFolders, // Will be default on by G12
featuremgmt.FlagQueryService, // Query Library
featuremgmt.FlagProvisioning,
featuremgmt.FlagInvestigationsBackend,
},
@@ -35,8 +35,7 @@ func TestIntegrationProvisioning(t *testing.T) {
EnableFeatureToggles: []string{
featuremgmt.FlagProvisioning,
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
featuremgmt.FlagKubernetesCliDashboards,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
@@ -373,7 +373,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
public onRestore = async (version: DecoratedRevisionModel): Promise<boolean> => {
let versionRsp;
if (config.featureToggles.kubernetesCliDashboards) {
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 {
@@ -136,7 +136,7 @@ export class VersionsEditView extends SceneObjectBase<VersionsEditViewState> imp
return;
}
let lhs, rhs;
if (config.featureToggles.kubernetesCliDashboards) {
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);
@@ -94,7 +94,7 @@ export class VersionsSettings extends PureComponent<Props, State> {
});
let lhs, rhs;
if (config.featureToggles.kubernetesCliDashboards) {
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);
@@ -19,7 +19,8 @@ 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.kubernetesCliDashboards ? id : version, dashboard!),
async () =>
await restoreDashboard(config.featureToggles.kubernetesClientDashboardsFolders ? id : version, dashboard!),
[]
);
const notifyApp = useAppNotification();