From 1fab107e792f8ddaade6230947978d93523781d3 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Sun, 28 Jan 2024 15:22:45 -0800 Subject: [PATCH] FeatureFlags: Avoid using cfg.IsFeatureToggleEnabled (#81407) --- pkg/api/accesscontrol.go | 2 +- .../commands/conflict_user_command.go | 22 +- pkg/cmd/grafana/apiserver/server.go | 6 +- pkg/registry/apis/datasource/standalone.go | 4 +- .../apis/datasource/standalone_services.go | 12 +- pkg/services/featuremgmt/manager.go | 20 +- pkg/services/featuremgmt/manager_test.go | 31 -- pkg/services/featuremgmt/models.go | 1 - pkg/services/featuremgmt/registry.go | 1 - pkg/services/featuremgmt/service.go | 18 +- pkg/services/featuremgmt/service_test.go | 67 +--- pkg/services/featuremgmt/toggles_gen.csv | 316 +++++++++--------- pkg/services/featuremgmt/toggles_gen_test.go | 2 - .../pluginaccesscontrol/accesscontrol.go | 4 +- pkg/services/sqlstore/database_config.go | 10 +- pkg/services/sqlstore/database_config_test.go | 7 +- .../migrations/accesscontrol/test/ac_test.go | 7 +- .../sqlstore/migrations/migrations.go | 23 +- pkg/services/sqlstore/sqlstore.go | 74 ++-- pkg/services/store/entity/server/config.go | 2 +- pkg/setting/provider.go | 6 - 21 files changed, 248 insertions(+), 387 deletions(-) diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index cd0d92fda1b..5ccdbf7e648 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -35,7 +35,7 @@ var ( // that HTTPServer needs func (hs *HTTPServer) declareFixedRoles() error { // Declare plugins roles - if err := pluginaccesscontrol.DeclareRBACRoles(hs.accesscontrolService, hs.Cfg); err != nil { + if err := pluginaccesscontrol.DeclareRBACRoles(hs.accesscontrolService, hs.Cfg, hs.Features); err != nil { return err } diff --git a/pkg/cmd/grafana-cli/commands/conflict_user_command.go b/pkg/cmd/grafana-cli/commands/conflict_user_command.go index 637ae88b75e..43cd08073a4 100644 --- a/pkg/cmd/grafana-cli/commands/conflict_user_command.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command.go @@ -33,7 +33,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, error) { +func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, *featuremgmt.FeatureManager, error) { configOptions := strings.Split(cmd.String("configOverrides"), " ") configOptions = append(configOptions, cmd.Args().Slice()...) cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{ @@ -43,17 +43,19 @@ func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, error) { }) if err != nil { - return nil, err + return nil, nil, err } - return cfg, nil + + features, err := featuremgmt.ProvideManagerService(cfg) + return cfg, features, err } func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx *cli.Context) (*ConflictResolver, error) { - cfg, err := initConflictCfg(cmd) + cfg, features, err := initConflictCfg(cmd) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to load configuration", err) } - s, err := getSqlStore(cfg) + s, err := getSqlStore(cfg, features) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get to sql", err) } @@ -67,11 +69,7 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx return nil, fmt.Errorf("%v: %w", "failed to get user service", err) } routing := routing.ProvideRegister() - featMgmt, err := featuremgmt.ProvideManagerService(cfg, nil) - if err != nil { - return nil, fmt.Errorf("%v: %w", "failed to get feature management service", err) - } - acService, err := acimpl.ProvideService(cfg, s, routing, nil, nil, nil, featMgmt) + acService, err := acimpl.ProvideService(cfg, s, routing, nil, nil, nil, features) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get access control", err) } @@ -80,13 +78,13 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx return &resolver, nil } -func getSqlStore(cfg *setting.Cfg) (*sqlstore.SQLStore, error) { +func getSqlStore(cfg *setting.Cfg, features featuremgmt.FeatureToggles) (*sqlstore.SQLStore, error) { tracer, err := tracing.ProvideService(cfg) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to initialize tracer service", err) } bus := bus.ProvideBus(tracer) - return sqlstore.ProvideService(cfg, &migrations.OSSMigrations{}, bus, tracer) + return sqlstore.ProvideService(cfg, features, &migrations.OSSMigrations{}, bus, tracer) } func runListConflictUsers() func(context *cli.Context) error { diff --git a/pkg/cmd/grafana/apiserver/server.go b/pkg/cmd/grafana/apiserver/server.go index bee150feab3..447567bb3de 100644 --- a/pkg/cmd/grafana/apiserver/server.go +++ b/pkg/cmd/grafana/apiserver/server.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" grafanaAPIServer "github.com/grafana/grafana/pkg/services/grafana-apiserver" "github.com/grafana/grafana/pkg/services/grafana-apiserver/utils" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" ) @@ -52,10 +51,7 @@ func (o *APIServerOptions) loadAPIGroupBuilders(args []string) error { case "example.grafana.app": o.builders = append(o.builders, example.NewTestingAPIBuilder()) case "featuretoggle.grafana.app": - features, err := featuremgmt.ProvideManagerService(&setting.Cfg{}, &licensing.OSSLicensingService{}) - if err != nil { - return err - } + features := featuremgmt.WithFeatureManager(setting.FeatureMgmtSettings{}, nil) // none... for now o.builders = append(o.builders, featuretoggle.NewFeatureFlagAPIBuilder(features)) case "testdata.datasource.grafana.app": ds, err := server.InitializeDataSourceAPIServer(g) diff --git a/pkg/registry/apis/datasource/standalone.go b/pkg/registry/apis/datasource/standalone.go index ea0fcff5cf0..34324bd11b8 100644 --- a/pkg/registry/apis/datasource/standalone.go +++ b/pkg/registry/apis/datasource/standalone.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request" "github.com/grafana/grafana/pkg/setting" testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource" @@ -19,6 +20,7 @@ import ( // This currently builds its dependencies manually and only works for testdata. func NewTestDataAPIServer(group string) (*DataSourceAPIBuilder, error) { pluginID := "grafana-testdata-datasource" + features := featuremgmt.WithFeatures() // None for now! if group != "testdata.datasource.grafana.app" { return nil, fmt.Errorf("only %s is currently supported", pluginID) @@ -47,7 +49,7 @@ func NewTestDataAPIServer(group string) (*DataSourceAPIBuilder, error) { return nil, err } - accessControl, pluginStore, dsService, dsCache, err := apiBuilderServices(cfg, pluginID) + accessControl, pluginStore, dsService, dsCache, err := apiBuilderServices(cfg, features, pluginID) if err != nil { return nil, err } diff --git a/pkg/registry/apis/datasource/standalone_services.go b/pkg/registry/apis/datasource/standalone_services.go index 085e25bc105..642c9a17c92 100644 --- a/pkg/registry/apis/datasource/standalone_services.go +++ b/pkg/registry/apis/datasource/standalone_services.go @@ -28,9 +28,7 @@ import ( "github.com/grafana/grafana/pkg/services/encryption/provider" encryptionService "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/kmsproviders/osskmsproviders" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/pluginsintegration/config" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" @@ -46,7 +44,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func apiBuilderServices(cfg *setting.Cfg, pluginID string) ( +func apiBuilderServices(cfg *setting.Cfg, features featuremgmt.FeatureToggles, pluginID string) ( *acimpl.AccessControl, *pluginstore.Service, *datasourceService.Service, @@ -60,16 +58,14 @@ func apiBuilderServices(cfg *setting.Cfg, pluginID string) ( return nil, nil, nil, nil, err } routeRegisterImpl := routing.ProvideRegister() - hooksService := hooks.ProvideService() - ossLicensingService := licensing.ProvideService(cfg, hooksService) - featureManager, err := featuremgmt.ProvideManagerService(cfg, ossLicensingService) + featureManager, err := featuremgmt.ProvideManagerService(cfg) if err != nil { return nil, nil, nil, nil, err } inProcBus := bus.ProvideBus(tracingService) - ossMigrations := migrations.ProvideOSSMigrations() - sqlStore, err := sqlstore.ProvideService(cfg, ossMigrations, inProcBus, tracingService) + ossMigrations := migrations.ProvideOSSMigrations(features) + sqlStore, err := sqlstore.ProvideService(cfg, features, ossMigrations, inProcBus, tracingService) if err != nil { return nil, nil, nil, nil, err } diff --git a/pkg/services/featuremgmt/manager.go b/pkg/services/featuremgmt/manager.go index 0dabd4d6f76..7aba0951001 100644 --- a/pkg/services/featuremgmt/manager.go +++ b/pkg/services/featuremgmt/manager.go @@ -6,7 +6,6 @@ import ( "reflect" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" ) @@ -20,12 +19,11 @@ type FeatureManager struct { Settings setting.FeatureMgmtSettings - licensing licensing.Licensing - flags map[string]*FeatureFlag - enabled map[string]bool // only the "on" values - startup map[string]bool // the explicit values registered at startup - warnings map[string]string // potential warnings about the flag - log log.Logger + flags map[string]*FeatureFlag + enabled map[string]bool // only the "on" values + startup map[string]bool // the explicit values registered at startup + warnings map[string]string // potential warnings about the flag + log log.Logger } // This will merge the flags with the current configuration @@ -62,10 +60,6 @@ func (fm *FeatureManager) registerFlags(flags ...FeatureFlag) { flag.RequiresDevMode = true } - if add.RequiresLicense { - flag.RequiresLicense = true - } - if add.RequiresRestart { flag.RequiresRestart = true } @@ -81,10 +75,6 @@ func (fm *FeatureManager) meetsRequirements(ff *FeatureFlag) (bool, string) { return false, "requires dev mode" } - if ff.RequiresLicense && (fm.licensing == nil || !fm.licensing.FeatureEnabled(ff.Name)) { - return false, "license requirement" - } - return true, "" } diff --git a/pkg/services/featuremgmt/manager_test.go b/pkg/services/featuremgmt/manager_test.go index c65e8585c16..434012df7bd 100644 --- a/pkg/services/featuremgmt/manager_test.go +++ b/pkg/services/featuremgmt/manager_test.go @@ -24,37 +24,6 @@ func TestFeatureManager(t *testing.T) { require.Equal(t, map[string]bool{"a": true}, ft.GetEnabled(context.Background())) }) - t.Run("check license validation", func(t *testing.T) { - ft := FeatureManager{ - flags: map[string]*FeatureFlag{}, - warnings: map[string]string{}, - } - ft.registerFlags(FeatureFlag{ - Name: "a", - RequiresLicense: true, - RequiresDevMode: true, - Expression: "true", - }, FeatureFlag{ - Name: "b", - Expression: "true", - }) - require.False(t, ft.IsEnabledGlobally("a")) - require.True(t, ft.IsEnabledGlobally("b")) - require.False(t, ft.IsEnabledGlobally("c")) // uknown flag - - // Try changing "requires license" - ft.registerFlags(FeatureFlag{ - Name: "a", - RequiresLicense: false, // shuld still require license! - }, FeatureFlag{ - Name: "b", - RequiresLicense: true, // expression is still "true" - }) - require.False(t, ft.IsEnabledGlobally("a")) - require.False(t, ft.IsEnabledGlobally("b")) - require.False(t, ft.IsEnabledGlobally("c")) - }) - t.Run("check description and docs configs", func(t *testing.T) { ft := FeatureManager{ flags: map[string]*FeatureFlag{}, diff --git a/pkg/services/featuremgmt/models.go b/pkg/services/featuremgmt/models.go index f941933002b..eb59416b074 100644 --- a/pkg/services/featuremgmt/models.go +++ b/pkg/services/featuremgmt/models.go @@ -127,7 +127,6 @@ type FeatureFlag struct { // Special behavior properties RequiresDevMode bool `json:"requiresDevMode,omitempty"` // can not be enabled in production - RequiresLicense bool `json:"requiresLicense,omitempty"` // Must be enabled in the license FrontendOnly bool `json:"frontend,omitempty"` // change is only seen in the frontend HideFromDocs bool `json:"hideFromDocs,omitempty"` // don't add the values to docs diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 896b7d53a0d..1dd8c7f2a5a 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -59,7 +59,6 @@ var ( Name: "publicDashboardsEmailSharing", Description: "Enables public dashboard sharing to be restricted to only allowed emails", Stage: FeatureStagePublicPreview, - RequiresLicense: true, Owner: grafanaSharingSquad, HideFromDocs: true, HideFromAdminPage: true, diff --git a/pkg/services/featuremgmt/service.go b/pkg/services/featuremgmt/service.go index 0c25dd77f00..137b8e174b4 100644 --- a/pkg/services/featuremgmt/service.go +++ b/pkg/services/featuremgmt/service.go @@ -5,7 +5,6 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" ) @@ -18,16 +17,15 @@ var ( }, []string{"name"}) ) -func ProvideManagerService(cfg *setting.Cfg, licensing licensing.Licensing) (*FeatureManager, error) { +func ProvideManagerService(cfg *setting.Cfg) (*FeatureManager, error) { mgmt := &FeatureManager{ - isDevMod: cfg.Env != setting.Prod, - licensing: licensing, - flags: make(map[string]*FeatureFlag, 30), - enabled: make(map[string]bool), - startup: make(map[string]bool), - warnings: make(map[string]string), - Settings: cfg.FeatureManagement, - log: log.New("featuremgmt"), + isDevMod: cfg.Env != setting.Prod, + flags: make(map[string]*FeatureFlag, 30), + enabled: make(map[string]bool), + startup: make(map[string]bool), + warnings: make(map[string]string), + Settings: cfg.FeatureManagement, + log: log.New("featuremgmt"), } // Register the standard flags diff --git a/pkg/services/featuremgmt/service_test.go b/pkg/services/featuremgmt/service_test.go index b69753fa362..49d85e98e06 100644 --- a/pkg/services/featuremgmt/service_test.go +++ b/pkg/services/featuremgmt/service_test.go @@ -5,40 +5,12 @@ import ( "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" ) func TestFeatureService(t *testing.T) { - license := stubLicenseServier{ - flags: []FeatureFlag{ - { - Name: "a.yes.default", - RequiresLicense: true, - Expression: "true", - }, - { - Name: "a.yes", - RequiresLicense: true, - Expression: "", - }, - { - Name: "b.no", - RequiresLicense: true, - }, - }, - enabled: map[string]bool{ - "a.yes.default": true, - "a.yes": true, - }, - } - require.False(t, license.FeatureEnabled("unknown")) - require.False(t, license.FeatureEnabled("b.no")) - require.True(t, license.FeatureEnabled("a.yes")) - require.True(t, license.FeatureEnabled("a.yes.default")) - cfg := setting.NewCfg() - mgmt, err := ProvideManagerService(cfg, license) + mgmt, err := ProvideManagerService(cfg) require.NoError(t, err) require.NotNil(t, mgmt) @@ -46,40 +18,3 @@ func TestFeatureService(t *testing.T) { require.False(t, mgmt.IsEnabledGlobally("a.yes.default")) require.False(t, mgmt.IsEnabledGlobally("a.yes")) // licensed, but not enabled } - -var ( - _ licensing.Licensing = (*stubLicenseServier)(nil) -) - -type stubLicenseServier struct { - flags []FeatureFlag - enabled map[string]bool -} - -func (s stubLicenseServier) Expiry() int64 { - return 100 -} - -func (s stubLicenseServier) Edition() string { - return "test" -} - -func (s stubLicenseServier) ContentDeliveryPrefix() string { - return "" -} - -func (s stubLicenseServier) LicenseURL(showAdminLicensingPage bool) string { - return "http://??" -} - -func (s stubLicenseServier) StateInfo() string { - return "ok" -} - -func (s stubLicenseServier) EnabledFeatures() map[string]bool { - return map[string]bool{} -} - -func (s stubLicenseServier) FeatureEnabled(feature string) bool { - return s.enabled[feature] -} diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 2b543058c1f..6da65ba3643 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -1,158 +1,158 @@ -Name,Stage,Owner,Created,requiresDevMode,RequiresLicense,RequiresRestart,FrontendOnly -disableEnvelopeEncryption,GA,@grafana/grafana-as-code,2022-05-24,false,false,false,false -live-service-web-worker,experimental,@grafana/grafana-app-platform-squad,2021-11-09,false,false,false,true -queryOverLive,experimental,@grafana/grafana-app-platform-squad,2022-01-05,false,false,false,true -panelTitleSearch,preview,@grafana/grafana-app-platform-squad,2022-02-15,false,false,false,false -publicDashboards,GA,@grafana/sharing-squad,2022-04-07,false,false,false,false -publicDashboardsEmailSharing,preview,@grafana/sharing-squad,2022-12-21,false,true,false,false -lokiExperimentalStreaming,experimental,@grafana/observability-logs,2023-06-19,false,false,false,false -featureHighlights,GA,@grafana/grafana-as-code,2022-02-03,false,false,false,false -migrationLocking,preview,@grafana/backend-platform,2022-02-15,false,false,false,false -storage,experimental,@grafana/grafana-app-platform-squad,2022-03-17,false,false,false,false -correlations,GA,@grafana/explore-squad,2022-09-16,false,false,false,false -exploreContentOutline,GA,@grafana/explore-squad,2023-11-03,false,false,false,true -datasourceQueryMultiStatus,experimental,@grafana/plugins-platform-backend,2022-05-03,false,false,false,false -traceToMetrics,experimental,@grafana/observability-traces-and-profiling,2022-03-07,false,false,false,true -autoMigrateOldPanels,preview,@grafana/dataviz-squad,2022-06-11,false,false,false,true -disableAngular,preview,@grafana/dataviz-squad,2023-03-23,false,false,false,true -canvasPanelNesting,experimental,@grafana/dataviz-squad,2022-05-31,false,false,false,true -newVizTooltips,preview,@grafana/dataviz-squad,2023-11-03,false,false,false,true -scenes,experimental,@grafana/dashboards-squad,2022-07-07,false,false,false,true -disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,2022-07-13,false,false,true,false -logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,2022-06-10,false,false,false,false -dataConnectionsConsole,GA,@grafana/plugins-platform-backend,2022-06-01,false,false,false,false -topnav,deprecated,@grafana/grafana-frontend-platform,2022-06-20,false,false,false,false -dockedMegaMenu,experimental,@grafana/grafana-frontend-platform,2023-09-18,false,false,false,true -returnToPrevious,experimental,@grafana/grafana-frontend-platform,2024-01-09,false,false,false,true -grpcServer,preview,@grafana/grafana-app-platform-squad,2022-09-27,false,false,false,false -unifiedStorage,experimental,@grafana/grafana-app-platform-squad,2022-12-01,true,false,true,false -cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,2022-11-28,false,false,false,false -redshiftAsyncQueryDataSupport,GA,@grafana/aws-datasources,2022-08-27,false,false,false,false -athenaAsyncQueryDataSupport,GA,@grafana/aws-datasources,2022-08-27,false,false,false,true -cloudwatchNewRegionsHandler,GA,@grafana/aws-datasources,2023-09-25,false,false,false,false -showDashboardValidationWarnings,experimental,@grafana/dashboards-squad,2022-10-14,false,false,false,false -mysqlAnsiQuotes,experimental,@grafana/backend-platform,2022-10-12,false,false,false,false -accessControlOnCall,preview,@grafana/identity-access-team,2022-10-19,false,false,false,false -nestedFolders,preview,@grafana/backend-platform,2022-10-22,false,false,false,false -nestedFolderPicker,GA,@grafana/grafana-frontend-platform,2023-07-24,false,false,false,true -disablePrometheusExemplarSampling,GA,@grafana/observability-metrics,2022-12-19,false,false,false,false -alertingBacktesting,experimental,@grafana/alerting-squad,2022-10-20,false,false,false,false -editPanelCSVDragAndDrop,experimental,@grafana/grafana-bi-squad,2022-12-20,false,false,false,true -alertingNoNormalState,preview,@grafana/alerting-squad,2023-01-14,false,false,false,false -logsContextDatasourceUi,GA,@grafana/observability-logs,2023-01-27,false,false,false,true -lokiQuerySplitting,GA,@grafana/observability-logs,2023-02-09,false,false,false,true -lokiQuerySplittingConfig,experimental,@grafana/observability-logs,2023-03-20,false,false,false,true -individualCookiePreferences,experimental,@grafana/backend-platform,2023-02-23,false,false,false,false -prometheusMetricEncyclopedia,GA,@grafana/observability-metrics,2023-03-07,false,false,false,true -influxdbBackendMigration,GA,@grafana/observability-metrics,2023-03-15,false,false,false,true -influxqlStreamingParser,experimental,@grafana/observability-metrics,2023-11-29,false,false,false,false -clientTokenRotation,GA,@grafana/identity-access-team,2023-03-23,false,false,false,false -prometheusDataplane,GA,@grafana/observability-metrics,2023-03-29,false,false,false,false -lokiMetricDataplane,GA,@grafana/observability-logs,2023-04-13,false,false,false,false -lokiLogsDataplane,experimental,@grafana/observability-logs,2023-07-13,false,false,false,false -dataplaneFrontendFallback,GA,@grafana/observability-metrics,2023-04-24,false,false,false,true -disableSSEDataplane,experimental,@grafana/observability-metrics,2023-04-24,false,false,false,false -alertStateHistoryLokiSecondary,experimental,@grafana/alerting-squad,2023-03-30,false,false,false,false -alertStateHistoryLokiPrimary,experimental,@grafana/alerting-squad,2023-03-30,false,false,false,false -alertStateHistoryLokiOnly,experimental,@grafana/alerting-squad,2023-03-30,false,false,false,false -unifiedRequestLog,experimental,@grafana/backend-platform,2023-03-31,false,false,false,false -renderAuthJWT,preview,@grafana/grafana-as-code,2023-04-03,false,false,false,false -externalServiceAuth,experimental,@grafana/identity-access-team,2023-04-11,true,false,false,false -refactorVariablesTimeRange,preview,@grafana/dashboards-squad,2023-06-06,false,false,false,false -enableElasticsearchBackendQuerying,GA,@grafana/observability-logs,2023-04-14,false,false,false,false -advancedDataSourcePicker,GA,@grafana/dashboards-squad,2023-04-14,false,false,false,true -faroDatasourceSelector,preview,@grafana/app-o11y,2023-05-04,false,false,false,true -enableDatagridEditing,preview,@grafana/grafana-bi-squad,2023-04-24,false,false,false,true -extraThemes,experimental,@grafana/grafana-frontend-platform,2023-05-10,false,false,false,true -lokiPredefinedOperations,experimental,@grafana/observability-logs,2023-06-02,false,false,false,true -pluginsFrontendSandbox,experimental,@grafana/plugins-platform-backend,2023-06-05,false,false,false,true -dashboardEmbed,experimental,@grafana/grafana-as-code,2023-07-06,false,false,false,true -frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,2023-07-05,false,false,false,true -sqlDatasourceDatabaseSelection,preview,@grafana/grafana-bi-squad,2023-06-06,false,false,false,true -lokiFormatQuery,experimental,@grafana/observability-logs,2023-06-21,false,false,false,true -cloudWatchLogsMonacoEditor,GA,@grafana/aws-datasources,2023-06-12,false,false,false,true -exploreScrollableLogsContainer,experimental,@grafana/observability-logs,2023-06-15,false,false,false,true -recordedQueriesMulti,GA,@grafana/observability-metrics,2023-06-14,false,false,false,false -pluginsDynamicAngularDetectionPatterns,experimental,@grafana/plugins-platform-backend,2023-06-26,false,false,false,false -vizAndWidgetSplit,experimental,@grafana/dashboards-squad,2023-06-27,false,false,false,true -prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,2023-07-05,false,false,false,true -logsExploreTableVisualisation,experimental,@grafana/observability-logs,2023-07-12,false,false,false,true -awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,2023-07-06,false,false,false,false -transformationsRedesign,GA,@grafana/observability-metrics,2023-07-12,false,false,false,true -mlExpressions,experimental,@grafana/alerting-squad,2023-07-13,false,false,false,false -traceQLStreaming,experimental,@grafana/observability-traces-and-profiling,2023-07-26,false,false,false,true -metricsSummary,experimental,@grafana/observability-traces-and-profiling,2023-08-28,false,false,false,true -grafanaAPIServerWithExperimentalAPIs,experimental,@grafana/grafana-app-platform-squad,2023-10-06,true,false,true,false -grafanaAPIServerEnsureKubectlAccess,experimental,@grafana/grafana-app-platform-squad,2023-12-06,true,false,true,false -featureToggleAdminPage,experimental,@grafana/grafana-operator-experience-squad,2023-07-18,false,false,true,false -awsAsyncQueryCaching,GA,@grafana/aws-datasources,2023-07-21,false,false,false,false -splitScopes,GA,@grafana/identity-access-team,2023-07-21,false,false,true,false -permissionsFilterRemoveSubquery,experimental,@grafana/backend-platform,2023-08-02,false,false,false,false -prometheusConfigOverhaulAuth,GA,@grafana/observability-metrics,2023-07-21,false,false,false,false -configurableSchedulerTick,experimental,@grafana/alerting-squad,2023-07-26,false,false,true,false -influxdbSqlSupport,GA,@grafana/observability-metrics,2023-08-02,false,false,true,false -alertingNoDataErrorExecution,GA,@grafana/alerting-squad,2023-08-15,false,false,true,false -angularDeprecationUI,experimental,@grafana/plugins-platform-backend,2023-08-29,false,false,false,true -dashgpt,preview,@grafana/dashboards-squad,2023-11-17,false,false,false,true -reportingRetries,preview,@grafana/sharing-squad,2023-08-31,false,false,true,false -sseGroupByDatasource,experimental,@grafana/observability-metrics,2023-09-07,false,false,false,false -requestInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,2023-09-11,false,false,false,false -libraryPanelRBAC,experimental,@grafana/dashboards-squad,2023-10-11,false,false,true,false -lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,2023-09-19,false,false,false,false -wargamesTesting,experimental,@grafana/hosted-grafana-team,2023-09-13,false,false,false,false -alertingInsights,GA,@grafana/alerting-squad,2023-09-14,false,false,false,true -externalCorePlugins,experimental,@grafana/plugins-platform-backend,2023-09-22,false,false,false,false -pluginsAPIMetrics,experimental,@grafana/plugins-platform-backend,2023-09-21,false,false,false,true -httpSLOLevels,experimental,@grafana/hosted-grafana-team,2023-09-22,false,false,true,false -idForwarding,experimental,@grafana/identity-access-team,2023-09-25,false,false,false,false -cloudWatchWildCardDimensionValues,GA,@grafana/aws-datasources,2023-09-27,false,false,false,false -externalServiceAccounts,privatePreview,@grafana/identity-access-team,2023-09-28,false,false,false,false -panelMonitoring,experimental,@grafana/dataviz-squad,2023-10-08,false,false,false,true -enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,2023-10-03,false,false,false,false -formatString,preview,@grafana/grafana-bi-squad,2023-10-13,false,false,false,true -transformationsVariableSupport,preview,@grafana/grafana-bi-squad,2023-10-04,false,false,false,true -kubernetesPlaylists,experimental,@grafana/grafana-app-platform-squad,2023-11-08,false,false,true,false -kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,2023-12-04,false,false,true,false -cloudWatchBatchQueries,preview,@grafana/aws-datasources,2023-10-20,false,false,false,false -recoveryThreshold,GA,@grafana/alerting-squad,2023-10-10,false,false,true,false -lokiStructuredMetadata,experimental,@grafana/observability-logs,2023-11-16,false,false,false,false -teamHttpHeaders,experimental,@grafana/identity-access-team,2023-10-17,false,false,false,false -awsDatasourcesNewFormStyling,experimental,@grafana/aws-datasources,2023-10-12,false,false,false,true -cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,2023-10-12,false,false,false,false -panelTitleSearchInV1,experimental,@grafana/backend-platform,2023-10-13,true,false,false,false -pluginsInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,2023-10-17,false,false,false,false -managedPluginsInstall,preview,@grafana/plugins-platform-backend,2023-10-18,false,false,false,false -prometheusPromQAIL,experimental,@grafana/observability-metrics,2023-10-19,false,false,false,true -addFieldFromCalculationStatFunctions,preview,@grafana/grafana-bi-squad,2023-11-03,false,false,false,true -alertmanagerRemoteSecondary,experimental,@grafana/alerting-squad,2023-10-30,false,false,false,false -alertmanagerRemotePrimary,experimental,@grafana/alerting-squad,2023-10-30,false,false,false,false -alertmanagerRemoteOnly,experimental,@grafana/alerting-squad,2023-10-30,false,false,false,false -annotationPermissionUpdate,experimental,@grafana/identity-access-team,2023-10-31,false,false,false,false -extractFieldsNameDeduplication,experimental,@grafana/grafana-bi-squad,2023-11-02,false,false,false,true -dashboardSceneForViewers,experimental,@grafana/dashboards-squad,2023-11-02,false,false,false,true -dashboardScene,experimental,@grafana/dashboards-squad,2023-11-13,false,false,false,true -panelFilterVariable,experimental,@grafana/dashboards-squad,2023-11-03,false,false,false,true -pdfTables,preview,@grafana/sharing-squad,2023-11-06,false,false,false,false -ssoSettingsApi,experimental,@grafana/identity-access-team,2023-11-08,false,false,false,false -canvasPanelPanZoom,preview,@grafana/dataviz-squad,2023-12-27,false,false,false,true -logsInfiniteScrolling,experimental,@grafana/observability-logs,2023-11-09,false,false,false,true -flameGraphItemCollapsing,experimental,@grafana/observability-traces-and-profiling,2023-11-09,false,false,false,true -alertingDetailsViewV2,experimental,@grafana/alerting-squad,2023-11-09,false,false,false,true -datatrails,experimental,@grafana/dashboards-squad,2023-11-15,false,false,false,true -alertingSimplifiedRouting,experimental,@grafana/alerting-squad,2023-11-10,false,false,false,false -logRowsPopoverMenu,GA,@grafana/observability-logs,2023-11-16,false,false,false,true -pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,2023-11-15,false,false,false,false -tableSharedCrosshair,experimental,@grafana/grafana-bi-squad,2023-12-12,false,false,false,true -regressionTransformation,preview,@grafana/grafana-bi-squad,2023-11-24,false,false,false,true -displayAnonymousStats,GA,@grafana/identity-access-team,2023-11-29,false,false,false,true -lokiQueryHints,GA,@grafana/observability-logs,2023-12-18,false,false,false,true -kubernetesFeatureToggles,experimental,@grafana/grafana-operator-experience-squad,2023-12-22,false,false,false,true -alertingPreviewUpgrade,preview,@grafana/alerting-squad,2024-01-03,false,false,true,false -enablePluginsTracingByDefault,experimental,@grafana/plugins-platform-backend,2024-01-09,false,false,true,false -cloudRBACRoles,experimental,@grafana/identity-access-team,2024-01-10,false,false,true,false -alertingQueryOptimization,GA,@grafana/alerting-squad,2024-01-10,false,false,false,false -newFolderPicker,experimental,@grafana/grafana-frontend-platform,2024-01-12,false,false,false,true -jitterAlertRules,GA,@grafana/alerting-squad,2024-01-17,false,false,true,false -jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,2024-01-17,false,false,true,false -onPremToCloudMigrations,experimental,@grafana/grafana-operator-experience-squad,2024-01-22,false,false,false,false -alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,2024-01-22,false,false,false,false +Name,Stage,Owner,Created,requiresDevMode,RequiresRestart,FrontendOnly +disableEnvelopeEncryption,GA,@grafana/grafana-as-code,2022-05-24,false,false,false +live-service-web-worker,experimental,@grafana/grafana-app-platform-squad,2021-11-09,false,false,true +queryOverLive,experimental,@grafana/grafana-app-platform-squad,2022-01-05,false,false,true +panelTitleSearch,preview,@grafana/grafana-app-platform-squad,2022-02-15,false,false,false +publicDashboards,GA,@grafana/sharing-squad,2022-04-07,false,false,false +publicDashboardsEmailSharing,preview,@grafana/sharing-squad,2022-12-21,false,false,false +lokiExperimentalStreaming,experimental,@grafana/observability-logs,2023-06-19,false,false,false +featureHighlights,GA,@grafana/grafana-as-code,2022-02-03,false,false,false +migrationLocking,preview,@grafana/backend-platform,2022-02-15,false,false,false +storage,experimental,@grafana/grafana-app-platform-squad,2022-03-17,false,false,false +correlations,GA,@grafana/explore-squad,2022-09-16,false,false,false +exploreContentOutline,GA,@grafana/explore-squad,2023-11-03,false,false,true +datasourceQueryMultiStatus,experimental,@grafana/plugins-platform-backend,2022-05-03,false,false,false +traceToMetrics,experimental,@grafana/observability-traces-and-profiling,2022-03-07,false,false,true +autoMigrateOldPanels,preview,@grafana/dataviz-squad,2022-06-11,false,false,true +disableAngular,preview,@grafana/dataviz-squad,2023-03-23,false,false,true +canvasPanelNesting,experimental,@grafana/dataviz-squad,2022-05-31,false,false,true +newVizTooltips,preview,@grafana/dataviz-squad,2023-11-03,false,false,true +scenes,experimental,@grafana/dashboards-squad,2022-07-07,false,false,true +disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,2022-07-13,false,true,false +logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,2022-06-10,false,false,false +dataConnectionsConsole,GA,@grafana/plugins-platform-backend,2022-06-01,false,false,false +topnav,deprecated,@grafana/grafana-frontend-platform,2022-06-20,false,false,false +dockedMegaMenu,experimental,@grafana/grafana-frontend-platform,2023-09-18,false,false,true +returnToPrevious,experimental,@grafana/grafana-frontend-platform,2024-01-09,false,false,true +grpcServer,preview,@grafana/grafana-app-platform-squad,2022-09-27,false,false,false +unifiedStorage,experimental,@grafana/grafana-app-platform-squad,2022-12-01,true,true,false +cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,2022-11-28,false,false,false +redshiftAsyncQueryDataSupport,GA,@grafana/aws-datasources,2022-08-27,false,false,false +athenaAsyncQueryDataSupport,GA,@grafana/aws-datasources,2022-08-27,false,false,true +cloudwatchNewRegionsHandler,GA,@grafana/aws-datasources,2023-09-25,false,false,false +showDashboardValidationWarnings,experimental,@grafana/dashboards-squad,2022-10-14,false,false,false +mysqlAnsiQuotes,experimental,@grafana/backend-platform,2022-10-12,false,false,false +accessControlOnCall,preview,@grafana/identity-access-team,2022-10-19,false,false,false +nestedFolders,preview,@grafana/backend-platform,2022-10-22,false,false,false +nestedFolderPicker,GA,@grafana/grafana-frontend-platform,2023-07-24,false,false,true +disablePrometheusExemplarSampling,GA,@grafana/observability-metrics,2022-12-19,false,false,false +alertingBacktesting,experimental,@grafana/alerting-squad,2022-10-20,false,false,false +editPanelCSVDragAndDrop,experimental,@grafana/grafana-bi-squad,2022-12-20,false,false,true +alertingNoNormalState,preview,@grafana/alerting-squad,2023-01-14,false,false,false +logsContextDatasourceUi,GA,@grafana/observability-logs,2023-01-27,false,false,true +lokiQuerySplitting,GA,@grafana/observability-logs,2023-02-09,false,false,true +lokiQuerySplittingConfig,experimental,@grafana/observability-logs,2023-03-20,false,false,true +individualCookiePreferences,experimental,@grafana/backend-platform,2023-02-23,false,false,false +prometheusMetricEncyclopedia,GA,@grafana/observability-metrics,2023-03-07,false,false,true +influxdbBackendMigration,GA,@grafana/observability-metrics,2023-03-15,false,false,true +influxqlStreamingParser,experimental,@grafana/observability-metrics,2023-11-29,false,false,false +clientTokenRotation,GA,@grafana/identity-access-team,2023-03-23,false,false,false +prometheusDataplane,GA,@grafana/observability-metrics,2023-03-29,false,false,false +lokiMetricDataplane,GA,@grafana/observability-logs,2023-04-13,false,false,false +lokiLogsDataplane,experimental,@grafana/observability-logs,2023-07-13,false,false,false +dataplaneFrontendFallback,GA,@grafana/observability-metrics,2023-04-24,false,false,true +disableSSEDataplane,experimental,@grafana/observability-metrics,2023-04-24,false,false,false +alertStateHistoryLokiSecondary,experimental,@grafana/alerting-squad,2023-03-30,false,false,false +alertStateHistoryLokiPrimary,experimental,@grafana/alerting-squad,2023-03-30,false,false,false +alertStateHistoryLokiOnly,experimental,@grafana/alerting-squad,2023-03-30,false,false,false +unifiedRequestLog,experimental,@grafana/backend-platform,2023-03-31,false,false,false +renderAuthJWT,preview,@grafana/grafana-as-code,2023-04-03,false,false,false +externalServiceAuth,experimental,@grafana/identity-access-team,2023-04-11,true,false,false +refactorVariablesTimeRange,preview,@grafana/dashboards-squad,2023-06-06,false,false,false +enableElasticsearchBackendQuerying,GA,@grafana/observability-logs,2023-04-14,false,false,false +advancedDataSourcePicker,GA,@grafana/dashboards-squad,2023-04-14,false,false,true +faroDatasourceSelector,preview,@grafana/app-o11y,2023-05-04,false,false,true +enableDatagridEditing,preview,@grafana/grafana-bi-squad,2023-04-24,false,false,true +extraThemes,experimental,@grafana/grafana-frontend-platform,2023-05-10,false,false,true +lokiPredefinedOperations,experimental,@grafana/observability-logs,2023-06-02,false,false,true +pluginsFrontendSandbox,experimental,@grafana/plugins-platform-backend,2023-06-05,false,false,true +dashboardEmbed,experimental,@grafana/grafana-as-code,2023-07-06,false,false,true +frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,2023-07-05,false,false,true +sqlDatasourceDatabaseSelection,preview,@grafana/grafana-bi-squad,2023-06-06,false,false,true +lokiFormatQuery,experimental,@grafana/observability-logs,2023-06-21,false,false,true +cloudWatchLogsMonacoEditor,GA,@grafana/aws-datasources,2023-06-12,false,false,true +exploreScrollableLogsContainer,experimental,@grafana/observability-logs,2023-06-15,false,false,true +recordedQueriesMulti,GA,@grafana/observability-metrics,2023-06-14,false,false,false +pluginsDynamicAngularDetectionPatterns,experimental,@grafana/plugins-platform-backend,2023-06-26,false,false,false +vizAndWidgetSplit,experimental,@grafana/dashboards-squad,2023-06-27,false,false,true +prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,2023-07-05,false,false,true +logsExploreTableVisualisation,experimental,@grafana/observability-logs,2023-07-12,false,false,true +awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,2023-07-06,false,false,false +transformationsRedesign,GA,@grafana/observability-metrics,2023-07-12,false,false,true +mlExpressions,experimental,@grafana/alerting-squad,2023-07-13,false,false,false +traceQLStreaming,experimental,@grafana/observability-traces-and-profiling,2023-07-26,false,false,true +metricsSummary,experimental,@grafana/observability-traces-and-profiling,2023-08-28,false,false,true +grafanaAPIServerWithExperimentalAPIs,experimental,@grafana/grafana-app-platform-squad,2023-10-06,true,true,false +grafanaAPIServerEnsureKubectlAccess,experimental,@grafana/grafana-app-platform-squad,2023-12-06,true,true,false +featureToggleAdminPage,experimental,@grafana/grafana-operator-experience-squad,2023-07-18,false,true,false +awsAsyncQueryCaching,GA,@grafana/aws-datasources,2023-07-21,false,false,false +splitScopes,GA,@grafana/identity-access-team,2023-07-21,false,true,false +permissionsFilterRemoveSubquery,experimental,@grafana/backend-platform,2023-08-02,false,false,false +prometheusConfigOverhaulAuth,GA,@grafana/observability-metrics,2023-07-21,false,false,false +configurableSchedulerTick,experimental,@grafana/alerting-squad,2023-07-26,false,true,false +influxdbSqlSupport,GA,@grafana/observability-metrics,2023-08-02,false,true,false +alertingNoDataErrorExecution,GA,@grafana/alerting-squad,2023-08-15,false,true,false +angularDeprecationUI,experimental,@grafana/plugins-platform-backend,2023-08-29,false,false,true +dashgpt,preview,@grafana/dashboards-squad,2023-11-17,false,false,true +reportingRetries,preview,@grafana/sharing-squad,2023-08-31,false,true,false +sseGroupByDatasource,experimental,@grafana/observability-metrics,2023-09-07,false,false,false +requestInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,2023-09-11,false,false,false +libraryPanelRBAC,experimental,@grafana/dashboards-squad,2023-10-11,false,true,false +lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,2023-09-19,false,false,false +wargamesTesting,experimental,@grafana/hosted-grafana-team,2023-09-13,false,false,false +alertingInsights,GA,@grafana/alerting-squad,2023-09-14,false,false,true +externalCorePlugins,experimental,@grafana/plugins-platform-backend,2023-09-22,false,false,false +pluginsAPIMetrics,experimental,@grafana/plugins-platform-backend,2023-09-21,false,false,true +httpSLOLevels,experimental,@grafana/hosted-grafana-team,2023-09-22,false,true,false +idForwarding,experimental,@grafana/identity-access-team,2023-09-25,false,false,false +cloudWatchWildCardDimensionValues,GA,@grafana/aws-datasources,2023-09-27,false,false,false +externalServiceAccounts,privatePreview,@grafana/identity-access-team,2023-09-28,false,false,false +panelMonitoring,experimental,@grafana/dataviz-squad,2023-10-08,false,false,true +enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,2023-10-03,false,false,false +formatString,preview,@grafana/grafana-bi-squad,2023-10-13,false,false,true +transformationsVariableSupport,preview,@grafana/grafana-bi-squad,2023-10-04,false,false,true +kubernetesPlaylists,experimental,@grafana/grafana-app-platform-squad,2023-11-08,false,true,false +kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,2023-12-04,false,true,false +cloudWatchBatchQueries,preview,@grafana/aws-datasources,2023-10-20,false,false,false +recoveryThreshold,GA,@grafana/alerting-squad,2023-10-10,false,true,false +lokiStructuredMetadata,experimental,@grafana/observability-logs,2023-11-16,false,false,false +teamHttpHeaders,experimental,@grafana/identity-access-team,2023-10-17,false,false,false +awsDatasourcesNewFormStyling,experimental,@grafana/aws-datasources,2023-10-12,false,false,true +cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,2023-10-12,false,false,false +panelTitleSearchInV1,experimental,@grafana/backend-platform,2023-10-13,true,false,false +pluginsInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,2023-10-17,false,false,false +managedPluginsInstall,preview,@grafana/plugins-platform-backend,2023-10-18,false,false,false +prometheusPromQAIL,experimental,@grafana/observability-metrics,2023-10-19,false,false,true +addFieldFromCalculationStatFunctions,preview,@grafana/grafana-bi-squad,2023-11-03,false,false,true +alertmanagerRemoteSecondary,experimental,@grafana/alerting-squad,2023-10-30,false,false,false +alertmanagerRemotePrimary,experimental,@grafana/alerting-squad,2023-10-30,false,false,false +alertmanagerRemoteOnly,experimental,@grafana/alerting-squad,2023-10-30,false,false,false +annotationPermissionUpdate,experimental,@grafana/identity-access-team,2023-10-31,false,false,false +extractFieldsNameDeduplication,experimental,@grafana/grafana-bi-squad,2023-11-02,false,false,true +dashboardSceneForViewers,experimental,@grafana/dashboards-squad,2023-11-02,false,false,true +dashboardScene,experimental,@grafana/dashboards-squad,2023-11-13,false,false,true +panelFilterVariable,experimental,@grafana/dashboards-squad,2023-11-03,false,false,true +pdfTables,preview,@grafana/sharing-squad,2023-11-06,false,false,false +ssoSettingsApi,experimental,@grafana/identity-access-team,2023-11-08,false,false,false +canvasPanelPanZoom,preview,@grafana/dataviz-squad,2023-12-27,false,false,true +logsInfiniteScrolling,experimental,@grafana/observability-logs,2023-11-09,false,false,true +flameGraphItemCollapsing,experimental,@grafana/observability-traces-and-profiling,2023-11-09,false,false,true +alertingDetailsViewV2,experimental,@grafana/alerting-squad,2023-11-09,false,false,true +datatrails,experimental,@grafana/dashboards-squad,2023-11-15,false,false,true +alertingSimplifiedRouting,experimental,@grafana/alerting-squad,2023-11-10,false,false,false +logRowsPopoverMenu,GA,@grafana/observability-logs,2023-11-16,false,false,true +pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,2023-11-15,false,false,false +tableSharedCrosshair,experimental,@grafana/grafana-bi-squad,2023-12-12,false,false,true +regressionTransformation,preview,@grafana/grafana-bi-squad,2023-11-24,false,false,true +displayAnonymousStats,GA,@grafana/identity-access-team,2023-11-29,false,false,true +lokiQueryHints,GA,@grafana/observability-logs,2023-12-18,false,false,true +kubernetesFeatureToggles,experimental,@grafana/grafana-operator-experience-squad,2023-12-22,false,false,true +alertingPreviewUpgrade,preview,@grafana/alerting-squad,2024-01-03,false,true,false +enablePluginsTracingByDefault,experimental,@grafana/plugins-platform-backend,2024-01-09,false,true,false +cloudRBACRoles,experimental,@grafana/identity-access-team,2024-01-10,false,true,false +alertingQueryOptimization,GA,@grafana/alerting-squad,2024-01-10,false,false,false +newFolderPicker,experimental,@grafana/grafana-frontend-platform,2024-01-12,false,false,true +jitterAlertRules,GA,@grafana/alerting-squad,2024-01-17,false,true,false +jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,2024-01-17,false,true,false +onPremToCloudMigrations,experimental,@grafana/grafana-operator-experience-squad,2024-01-22,false,false,false +alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,2024-01-22,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen_test.go b/pkg/services/featuremgmt/toggles_gen_test.go index c144cb8a46c..9dd117f976f 100644 --- a/pkg/services/featuremgmt/toggles_gen_test.go +++ b/pkg/services/featuremgmt/toggles_gen_test.go @@ -218,7 +218,6 @@ func generateCSV() string { "Owner", //string(flag.Owner), "Created", "requiresDevMode", //strconv.FormatBool(flag.RequiresDevMode), - "RequiresLicense", //strconv.FormatBool(flag.RequiresLicense), "RequiresRestart", //strconv.FormatBool(flag.RequiresRestart), "FrontendOnly", //strconv.FormatBool(flag.FrontendOnly), }); err != nil { @@ -239,7 +238,6 @@ func generateCSV() string { string(flag.Owner), dateFormatter(flag.Created), strconv.FormatBool(flag.RequiresDevMode), - strconv.FormatBool(flag.RequiresLicense), strconv.FormatBool(flag.RequiresRestart), strconv.FormatBool(flag.FrontendOnly), }); err != nil { diff --git a/pkg/services/pluginsintegration/pluginaccesscontrol/accesscontrol.go b/pkg/services/pluginsintegration/pluginaccesscontrol/accesscontrol.go index b462bc14ac1..1a7840d545c 100644 --- a/pkg/services/pluginsintegration/pluginaccesscontrol/accesscontrol.go +++ b/pkg/services/pluginsintegration/pluginaccesscontrol/accesscontrol.go @@ -30,7 +30,7 @@ func ReqCanAdminPlugins(cfg *setting.Cfg) func(rc *contextmodel.ReqContext) bool } } -func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg) error { +func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg, features featuremgmt.FeatureToggles) error { AppPluginsReader := ac.RoleRegistration{ Role: ac.RoleDTO{ Name: ac.FixedRolePrefix + "plugins.app:reader", @@ -69,7 +69,7 @@ func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg) error { } if !cfg.PluginAdminEnabled || - (cfg.PluginAdminExternalManageEnabled && !cfg.IsFeatureToggleEnabled(featuremgmt.FlagManagedPluginsInstall)) { + (cfg.PluginAdminExternalManageEnabled && !features.IsEnabledGlobally(featuremgmt.FlagManagedPluginsInstall)) { PluginsMaintainer.Grants = []string{} } diff --git a/pkg/services/sqlstore/database_config.go b/pkg/services/sqlstore/database_config.go index af20267f8fa..1b627d0515e 100644 --- a/pkg/services/sqlstore/database_config.go +++ b/pkg/services/sqlstore/database_config.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/go-sql-driver/mysql" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" @@ -45,7 +46,7 @@ type DatabaseConfig struct { TransactionRetries int } -func NewDatabaseConfig(cfg *setting.Cfg) (*DatabaseConfig, error) { +func NewDatabaseConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles) (*DatabaseConfig, error) { if cfg == nil { return nil, errors.New("cfg cannot be nil") } @@ -55,7 +56,7 @@ func NewDatabaseConfig(cfg *setting.Cfg) (*DatabaseConfig, error) { return nil, err } - if err := dbCfg.buildConnectionString(cfg); err != nil { + if err := dbCfg.buildConnectionString(cfg, features); err != nil { return nil, err } @@ -120,7 +121,7 @@ func (dbCfg *DatabaseConfig) readConfig(cfg *setting.Cfg) error { return nil } -func (dbCfg *DatabaseConfig) buildConnectionString(cfg *setting.Cfg) error { +func (dbCfg *DatabaseConfig) buildConnectionString(cfg *setting.Cfg, features featuremgmt.FeatureToggles) error { if dbCfg.ConnectionString != "" { return nil } @@ -154,8 +155,7 @@ func (dbCfg *DatabaseConfig) buildConnectionString(cfg *setting.Cfg) error { cnnstr += fmt.Sprintf("&transaction_isolation=%s", val) } - // nolint:staticcheck - if cfg.IsFeatureToggleEnabled(featuremgmt.FlagMysqlAnsiQuotes) { + if features != nil && features.IsEnabledGlobally(featuremgmt.FlagMysqlAnsiQuotes) { cnnstr += "&sql_mode='ANSI_QUOTES'" } diff --git a/pkg/services/sqlstore/database_config_test.go b/pkg/services/sqlstore/database_config_test.go index 9fba6b55a23..942cdc23561 100644 --- a/pkg/services/sqlstore/database_config_test.go +++ b/pkg/services/sqlstore/database_config_test.go @@ -5,10 +5,11 @@ import ( "net/url" "testing" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" ) type databaseConfigTest struct { @@ -109,7 +110,7 @@ func TestIntegrationSQLConnectionString(t *testing.T) { for _, testCase := range databaseConfigTestCases { t.Run(testCase.name, func(t *testing.T) { cfg := makeDatabaseTestConfig(t, testCase) - dbCfg, err := NewDatabaseConfig(cfg) + dbCfg, err := NewDatabaseConfig(cfg, testCase.features) require.Equal(t, testCase.err, err) if testCase.expConnStr != "" { assert.Equal(t, testCase.expConnStr, dbCfg.ConnectionString) diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go index 7e7f74f1666..ef60e500799 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go @@ -154,9 +154,7 @@ func TestMigrations(t *testing.T) { desc: "with editors can admin", config: &setting.Cfg{ EditorsCanAdmin: true, - // nolint:staticcheck - IsFeatureToggleEnabled: func(key string) bool { return key == "accesscontrol" }, - Raw: ini.Empty(), + Raw: ini.Empty(), }, expectedRolePerms: map[string][]rawPermission{ "managed:users:1:permissions": {{Action: "teams:read", Scope: team1Scope}}, @@ -262,9 +260,6 @@ func setupTestDB(t *testing.T) *xorm.Engine { mg := migrator.NewMigrator(x, &setting.Cfg{ Logger: log.New("acmigration.test"), Raw: ini.Empty(), - IsFeatureToggleEnabled: func(key string) bool { - return true - }, }) migrations := &migrations.OSSMigrations{} migrations.AddMigration(mg) diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index f7e9efa7ce2..e4602dada79 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -21,13 +21,14 @@ import ( // specifically added type OSSMigrations struct { + features featuremgmt.FeatureToggles } -func ProvideOSSMigrations() *OSSMigrations { - return &OSSMigrations{} +func ProvideOSSMigrations(features featuremgmt.FeatureToggles) *OSSMigrations { + return &OSSMigrations{features} } -func (*OSSMigrations) AddMigration(mg *Migrator) { +func (oss *OSSMigrations) AddMigration(mg *Migrator) { mg.AddCreateMigration() addUserMigrations(mg) addTempUserMigrations(mg) @@ -94,12 +95,8 @@ func (*OSSMigrations) AddMigration(mg *Migrator) { AddExternalAlertmanagerToDatasourceMigration(mg) addFolderMigrations(mg) - // nolint:staticcheck - if mg.Cfg != nil && mg.Cfg.IsFeatureToggleEnabled != nil { - // nolint:staticcheck - if mg.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagExternalServiceAuth) { - oauthserver.AddMigration(mg) - } + if oss.features != nil && oss.features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth) { + oauthserver.AddMigration(mg) } anonservice.AddMigration(mg) @@ -115,12 +112,8 @@ func (*OSSMigrations) AddMigration(mg *Migrator) { ualert.CreateOrgMigratedKVStoreEntries(mg) // https://github.com/grafana/identity-access-team/issues/546: tracks removal of the feature toggle from the annotation permission migration - // nolint:staticcheck - if mg.Cfg != nil && mg.Cfg.IsFeatureToggleEnabled != nil { - // nolint:staticcheck - if mg.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAnnotationPermissionUpdate) { - accesscontrol.AddManagedDashboardAnnotationActionsMigration(mg) - } + if oss.features != nil && oss.features.IsEnabledGlobally(featuremgmt.FlagAnnotationPermissionUpdate) { + accesscontrol.AddManagedDashboardAnnotationActionsMigration(mg) } addKVStoreMySQLValueTypeLongTextMigration(mg) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index faf272ce9f3..334c72ad4f5 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -38,6 +38,7 @@ type ContextSessionKey struct{} type SQLStore struct { Cfg *setting.Cfg + features featuremgmt.FeatureToggles sqlxsession *session.SessionDB bus bus.Bus @@ -52,7 +53,10 @@ type SQLStore struct { recursiveQueriesMu sync.Mutex } -func ProvideService(cfg *setting.Cfg, migrations registry.DatabaseMigrator, bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { +func ProvideService(cfg *setting.Cfg, + features featuremgmt.FeatureToggles, + migrations registry.DatabaseMigrator, + bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { // This change will make xorm use an empty default schema for postgres and // by that mimic the functionality of how it was functioning before // xorm's changes above. @@ -61,9 +65,9 @@ func ProvideService(cfg *setting.Cfg, migrations registry.DatabaseMigrator, bus if err != nil { return nil, err } + s.features = features - // nolint:staticcheck - if err := s.Migrate(cfg.IsFeatureToggleEnabled(featuremgmt.FlagMigrationLocking)); err != nil { + if err := s.Migrate(features.IsEnabledGlobally(featuremgmt.FlagMigrationLocking)); err != nil { return nil, err } @@ -87,8 +91,8 @@ func ProvideService(cfg *setting.Cfg, migrations registry.DatabaseMigrator, bus return s, nil } -func ProvideServiceForTests(cfg *setting.Cfg, migrations registry.DatabaseMigrator) (*SQLStore, error) { - return initTestDB(cfg, migrations, InitTestDBOpt{EnsureDefaultOrgAndUser: true}) +func ProvideServiceForTests(cfg *setting.Cfg, features featuremgmt.FeatureToggles, migrations registry.DatabaseMigrator) (*SQLStore, error) { + return initTestDB(cfg, features, migrations, InitTestDBOpt{EnsureDefaultOrgAndUser: true}) } func newSQLStore(cfg *setting.Cfg, engine *xorm.Engine, @@ -239,7 +243,7 @@ func (ss *SQLStore) initEngine(engine *xorm.Engine) error { return nil } - dbCfg, err := NewDatabaseConfig(ss.Cfg) + dbCfg, err := NewDatabaseConfig(ss.Cfg, ss.features) if err != nil { return err } @@ -402,15 +406,11 @@ type InitTestDBOpt struct { FeatureFlags []string } -var featuresEnabledDuringTests = []string{ - featuremgmt.FlagPanelTitleSearch, - featuremgmt.FlagUnifiedStorage, -} - // InitTestDBWithMigration initializes the test DB given custom migrations. func InitTestDBWithMigration(t ITestDB, migration registry.DatabaseMigrator, opts ...InitTestDBOpt) *SQLStore { t.Helper() - store, err := initTestDB(setting.NewCfg(), migration, opts...) + features := getFeaturesForTesting(opts...) + store, err := initTestDB(setting.NewCfg(), features, migration, opts...) if err != nil { t.Fatalf("failed to initialize sql store: %s", err) } @@ -420,7 +420,9 @@ func InitTestDBWithMigration(t ITestDB, migration registry.DatabaseMigrator, opt // InitTestDB initializes the test DB. func InitTestDB(t ITestDB, opts ...InitTestDBOpt) *SQLStore { t.Helper() - store, err := initTestDB(setting.NewCfg(), &migrations.OSSMigrations{}, opts...) + features := getFeaturesForTesting(opts...) + + store, err := initTestDB(setting.NewCfg(), features, migrations.ProvideOSSMigrations(features), opts...) if err != nil { t.Fatalf("failed to initialize sql store: %s", err) } @@ -432,8 +434,26 @@ func InitTestDBWithCfg(t ITestDB, opts ...InitTestDBOpt) (*SQLStore, *setting.Cf return store, store.Cfg } +func getFeaturesForTesting(opts ...InitTestDBOpt) featuremgmt.FeatureToggles { + featureKeys := []any{ + featuremgmt.FlagPanelTitleSearch, + featuremgmt.FlagUnifiedStorage, + } + for _, opt := range opts { + if len(opt.FeatureFlags) > 0 { + for _, f := range opt.FeatureFlags { + featureKeys = append(featureKeys, f) + } + } + } + return featuremgmt.WithFeatures(featureKeys...) +} + //nolint:gocyclo -func initTestDB(testCfg *setting.Cfg, migration registry.DatabaseMigrator, opts ...InitTestDBOpt) (*SQLStore, error) { +func initTestDB(testCfg *setting.Cfg, + features featuremgmt.FeatureToggles, + migration registry.DatabaseMigrator, + opts ...InitTestDBOpt) (*SQLStore, error) { testSQLStoreMutex.Lock() defer testSQLStoreMutex.Unlock() @@ -441,14 +461,6 @@ func initTestDB(testCfg *setting.Cfg, migration registry.DatabaseMigrator, opts opts = []InitTestDBOpt{{EnsureDefaultOrgAndUser: false, FeatureFlags: []string{}}} } - features := make([]string, len(featuresEnabledDuringTests)) - copy(features, featuresEnabledDuringTests) - for _, opt := range opts { - if len(opt.FeatureFlags) > 0 { - features = append(features, opt.FeatureFlags...) - } - } - if testSQLStore == nil { dbType := migrator.SQLite @@ -460,14 +472,7 @@ func initTestDB(testCfg *setting.Cfg, migration registry.DatabaseMigrator, opts // set test db config cfg := setting.NewCfg() // nolint:staticcheck - cfg.IsFeatureToggleEnabled = func(key string) bool { - for _, enabledFeature := range features { - if enabledFeature == key { - return true - } - } - return false - } + cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally sec, err := cfg.Raw.NewSection("database") if err != nil { @@ -555,14 +560,7 @@ func initTestDB(testCfg *setting.Cfg, migration registry.DatabaseMigrator, opts } // nolint:staticcheck - testSQLStore.Cfg.IsFeatureToggleEnabled = func(key string) bool { - for _, enabledFeature := range features { - if enabledFeature == key { - return true - } - } - return false - } + testSQLStore.Cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally if err := testSQLStore.Dialect.TruncateDBTables(testSQLStore.GetEngine()); err != nil { return nil, err diff --git a/pkg/services/store/entity/server/config.go b/pkg/services/store/entity/server/config.go index cef38e756da..22bdba14646 100644 --- a/pkg/services/store/entity/server/config.go +++ b/pkg/services/store/entity/server/config.go @@ -41,7 +41,7 @@ func newConfig(cfg *setting.Cfg) *config { host := fmt.Sprintf("%s:%d", ip, port) return &config{ - enabled: true, // cfg.IsFeatureToggleEnabled(featuremgmt.FlagGrafanaStorageServer), + enabled: true, devMode: cfg.Env == setting.Dev, ip: ip, port: port, diff --git a/pkg/setting/provider.go b/pkg/setting/provider.go index c2d89e072e4..a66bef430c2 100644 --- a/pkg/setting/provider.go +++ b/pkg/setting/provider.go @@ -144,12 +144,6 @@ func (o *OSSImpl) Section(section string) Section { func (*OSSImpl) RegisterReloadHandler(string, ReloadHandler) {} -// Deprecated: use feature toggles -func (o *OSSImpl) IsFeatureToggleEnabled(name string) bool { - // nolint:staticcheck - return o.Cfg.IsFeatureToggleEnabled(name) -} - type keyValImpl struct { key *ini.Key }