mirror of https://github.com/grafana/grafana.git
				
				
				
			PublicDashboards: Add setting to disable the feature (#78894)
* Replace feature toggle with configuration setting * Fix permission alert * Update documentation * Add back feature toggle * revert unwanted commited changes * fix tests * run prettier * Update SharePublicDashboard.test.tsx * fix linter and frontend tests * Update api.go * Apply docs edit from code review Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com> * Update index.md * Update docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * add isPublicDashboardsEnabled + test * fix test * update ff description in registry * move isPublicDashboardsEnabled * revert getConfig() update --------- Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com> Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									ef60c90dfa
								
							
						
					
					
						commit
						fdaf6e3f2e
					
				|  | @ -1762,3 +1762,8 @@ hidden_toggles = | |||
| 
 | ||||
| # Disables updating specific feature toggles in the feature management page | ||||
| read_only_toggles = | ||||
| 
 | ||||
| #################################### Public Dashboards ##################################### | ||||
| [public_dashboards] | ||||
| # Set to false to disable public dashboards | ||||
| enabled = true | ||||
|  |  | |||
|  | @ -1615,3 +1615,9 @@ | |||
| ;hidden_toggles = | ||||
| # Disable updating specific feature toggles in the feature management page | ||||
| ;read_only_toggles = | ||||
| 
 | ||||
| #################################### Public Dashboards ##################################### | ||||
| [public_dashboards] | ||||
| # Set to false to disable public dashboards | ||||
| ;enabled = true | ||||
| 
 | ||||
|  |  | |||
|  | @ -62,7 +62,7 @@ Dashboard insights show the following information: | |||
| 
 | ||||
| {{% admonition type="note" %}} | ||||
| 
 | ||||
| If you've enabled the `publicDashboards` feature toggle, you'll also see a Public dashboards tab in your analytics. | ||||
| If public dashboards are [enabled]({{< relref "../../setup-grafana/configure-grafana/#public_dashboards" >}}), you'll also see a **Public dashboards** tab in your analytics. | ||||
| 
 | ||||
| {{% /admonition %}} | ||||
| 
 | ||||
|  |  | |||
|  | @ -2538,3 +2538,11 @@ Move an app plugin (referenced by its id), including all its pages, to a specifi | |||
| 
 | ||||
| Move an individual app plugin page (referenced by its `path` field) to a specific navigation section. | ||||
| Format: `<pageUrl> = <sectionId> <sortWeight>` | ||||
| 
 | ||||
| ## [public_dashboards] | ||||
| 
 | ||||
| This section configures the [public dashboards]({{< relref "../../dashboards/dashboard-public" >}}) feature. | ||||
| 
 | ||||
| ### enabled | ||||
| 
 | ||||
| Set this to `false` to disable the public dashboards feature. This prevents users from creating new public dashboards and disables existing ones. | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ Some features are enabled by default. You can disable these feature by setting t | |||
| | Feature toggle name                  | Description                                                                                                                                                                                                                  | Enabled by default | | ||||
| | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | ||||
| | `disableEnvelopeEncryption`          | Disable envelope encryption (emergency only)                                                                                                                                                                                 |                    | | ||||
| | `publicDashboards`                   | Enables public access to dashboards                                                                                                                                                                                          | Yes                | | ||||
| | `publicDashboards`                   | [Deprecated] Public dashboards are now enabled by default; to disable them, use the configuration setting. This feature toggle will be removed in the next major version.                                                    | Yes                | | ||||
| | `featureHighlights`                  | Highlight Grafana Enterprise features                                                                                                                                                                                        |                    | | ||||
| | `exploreContentOutline`              | Content outline sidebar                                                                                                                                                                                                      | Yes                | | ||||
| | `newVizTooltips`                     | New visualizations tooltips UX                                                                                                                                                                                               |                    | | ||||
|  |  | |||
|  | @ -117,10 +117,10 @@ docker run -d -p 3000:3000 --name=grafana \ | |||
| Grafana supports specifying custom configuration settings using [environment variables]({{< relref "../../../setup-grafana/configure-grafana#override-configuration-with-environment-variables" >}}). | ||||
| 
 | ||||
| ```bash | ||||
| # enabling public dashboard feature | ||||
| # enable debug logs | ||||
| 
 | ||||
| docker run -d -p 3000:3000 --name=grafana \ | ||||
|   -e "GF_FEATURE_TOGGLES_ENABLE=publicDashboards" \ | ||||
|   -e "GF_LOG_LEVEL=debug" \ | ||||
|   grafana/grafana-enterprise | ||||
| ``` | ||||
| 
 | ||||
|  |  | |||
|  | @ -148,6 +148,7 @@ export interface BootData { | |||
|  */ | ||||
| export interface GrafanaConfig { | ||||
|   publicDashboardAccessToken?: string; | ||||
|   publicDashboardsEnabled: boolean; | ||||
|   snapshotEnabled: boolean; | ||||
|   datasources: { [str: string]: DataSourceInstanceSettings }; | ||||
|   panels: { [key: string]: PanelPluginMeta }; | ||||
|  |  | |||
|  | @ -36,6 +36,7 @@ export type AppPluginConfig = { | |||
| 
 | ||||
| export class GrafanaBootConfig implements GrafanaConfig { | ||||
|   publicDashboardAccessToken?: string; | ||||
|   publicDashboardsEnabled = true; | ||||
|   snapshotEnabled = true; | ||||
|   datasources: { [str: string]: DataSourceInstanceSettings } = {}; | ||||
|   panels: { [key: string]: PanelPluginMeta } = {}; | ||||
|  |  | |||
|  | @ -161,7 +161,7 @@ func (hs *HTTPServer) registerRoutes() { | |||
| 		r.Get("/d-embed", reqSignedIn, middleware.AddAllowEmbeddingHeader(), hs.Index) | ||||
| 	} | ||||
| 
 | ||||
| 	if hs.Features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) { | ||||
| 	if hs.Features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) && hs.Cfg.PublicDashboardsEnabled { | ||||
| 		// list public dashboards
 | ||||
| 		r.Get("/public-dashboards/list", reqSignedIn, hs.Index) | ||||
| 
 | ||||
|  |  | |||
|  | @ -90,9 +90,8 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response | |||
| 		err                    error | ||||
| 	) | ||||
| 
 | ||||
| 	// If public dashboards is enabled and we have a public dashboard, update meta
 | ||||
| 	// values
 | ||||
| 	if hs.Features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) { | ||||
| 	// If public dashboards is enabled and we have a public dashboard, update meta values
 | ||||
| 	if hs.Features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) && hs.Cfg.PublicDashboardsEnabled { | ||||
| 		publicDashboard, err := hs.PublicDashboardsApi.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.SignedInUser.GetOrgID(), dash.UID) | ||||
| 		if err != nil && !errors.Is(err, publicdashboardModels.ErrPublicDashboardNotFound) { | ||||
| 			return response.Error(http.StatusInternalServerError, "Error while retrieving public dashboards", err) | ||||
|  |  | |||
|  | @ -272,7 +272,7 @@ func TestHTTPServer_DeleteDashboardByUID_AccessControl(t *testing.T) { | |||
| 			pubDashService := publicdashboards.NewFakePublicDashboardService(t) | ||||
| 			pubDashService.On("DeleteByDashboard", mock.Anything, mock.Anything).Return(nil).Maybe() | ||||
| 			middleware := publicdashboards.NewFakePublicDashboardMiddleware(t) | ||||
| 			hs.PublicDashboardsApi = api.ProvideApi(pubDashService, nil, hs.AccessControl, featuremgmt.WithFeatures(), middleware) | ||||
| 			hs.PublicDashboardsApi = api.ProvideApi(pubDashService, nil, hs.AccessControl, featuremgmt.WithFeatures(), middleware, hs.Cfg) | ||||
| 
 | ||||
| 			guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService) | ||||
| 		}) | ||||
|  |  | |||
|  | @ -238,6 +238,7 @@ type FrontendSettingsDTO struct { | |||
| 	GeomapDisableCustomBaseLayer bool            `json:"geomapDisableCustomBaseLayer"` | ||||
| 
 | ||||
| 	PublicDashboardAccessToken string `json:"publicDashboardAccessToken"` | ||||
| 	PublicDashboardsEnabled    bool   `json:"publicDashboardsEnabled"` | ||||
| 
 | ||||
| 	DateFormats setting.DateFormats `json:"dateFormats,omitempty"` | ||||
| 
 | ||||
|  |  | |||
|  | @ -158,6 +158,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro | |||
| 		SecureSocksDSProxyEnabled:           hs.Cfg.SecureSocksDSProxy.Enabled && hs.Cfg.SecureSocksDSProxy.ShowUI, | ||||
| 		DisableFrontendSandboxForPlugins:    hs.Cfg.DisableFrontendSandboxForPlugins, | ||||
| 		PublicDashboardAccessToken:          c.PublicDashboardAccessToken, | ||||
| 		PublicDashboardsEnabled:             hs.Cfg.PublicDashboardsEnabled, | ||||
| 		SharedWithMeFolderUID:               folder.SharedWithMeFolderUID, | ||||
| 
 | ||||
| 		BuildInfo: dtos.FrontendSettingsBuildInfoDTO{ | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ var ( | |||
| 		}, | ||||
| 		{ | ||||
| 			Name:           "publicDashboards", | ||||
| 			Description:    "Enables public access to dashboards", | ||||
| 			Description:    "[Deprecated] Public dashboards are now enabled by default; to disable them, use the configuration setting. This feature toggle will be removed in the next major version.", | ||||
| 			Stage:          FeatureStageGeneralAvailability, | ||||
| 			Owner:          grafanaSharingSquad, | ||||
| 			Expression:     "true", // enabled by default
 | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ const ( | |||
| 	FlagPanelTitleSearch = "panelTitleSearch" | ||||
| 
 | ||||
| 	// FlagPublicDashboards
 | ||||
| 	// Enables public access to dashboards
 | ||||
| 	// [Deprecated] Public dashboards are now enabled by default; to disable them, use the configuration setting. This feature toggle will be removed in the next major version.
 | ||||
| 	FlagPublicDashboards = "publicDashboards" | ||||
| 
 | ||||
| 	// FlagPublicDashboardsEmailSharing
 | ||||
|  |  | |||
|  | @ -358,7 +358,7 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt | |||
| 			Icon:     "library-panel", | ||||
| 		}) | ||||
| 
 | ||||
| 		if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPublicDashboards) { | ||||
| 		if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPublicDashboards) && s.cfg.PublicDashboardsEnabled { | ||||
| 			dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ | ||||
| 				Text: "Public dashboards", | ||||
| 				Id:   "dashboards/public", | ||||
|  |  | |||
|  | @ -17,16 +17,19 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/publicdashboards" | ||||
| 	. "github.com/grafana/grafana/pkg/services/publicdashboards/models" | ||||
| 	"github.com/grafana/grafana/pkg/services/publicdashboards/validation" | ||||
| 	"github.com/grafana/grafana/pkg/setting" | ||||
| 	"github.com/grafana/grafana/pkg/web" | ||||
| ) | ||||
| 
 | ||||
| type Api struct { | ||||
| 	PublicDashboardService publicdashboards.Service | ||||
| 	RouteRegister          routing.RouteRegister | ||||
| 	AccessControl          accesscontrol.AccessControl | ||||
| 	Features               *featuremgmt.FeatureManager | ||||
| 	Log                    log.Logger | ||||
| 	Middleware             publicdashboards.Middleware | ||||
| 
 | ||||
| 	accessControl accesscontrol.AccessControl | ||||
| 	cfg           *setting.Cfg | ||||
| 	features      *featuremgmt.FeatureManager | ||||
| 	log           log.Logger | ||||
| 	routeRegister routing.RouteRegister | ||||
| } | ||||
| 
 | ||||
| func ProvideApi( | ||||
|  | @ -35,21 +38,27 @@ func ProvideApi( | |||
| 	ac accesscontrol.AccessControl, | ||||
| 	features *featuremgmt.FeatureManager, | ||||
| 	md publicdashboards.Middleware, | ||||
| 	cfg *setting.Cfg, | ||||
| ) *Api { | ||||
| 	api := &Api{ | ||||
| 		PublicDashboardService: pd, | ||||
| 		RouteRegister:          rr, | ||||
| 		AccessControl:          ac, | ||||
| 		Features:               features, | ||||
| 		Log:                    log.New("publicdashboards.api"), | ||||
| 		Middleware:             md, | ||||
| 		accessControl:          ac, | ||||
| 		cfg:                    cfg, | ||||
| 		features:               features, | ||||
| 		log:                    log.New("publicdashboards.api"), | ||||
| 		routeRegister:          rr, | ||||
| 	} | ||||
| 
 | ||||
| 	// attach api if PublicDashboards feature flag is enabled
 | ||||
| 	if features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) { | ||||
| 	// register endpoints if the feature is enabled
 | ||||
| 	if features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) && cfg.PublicDashboardsEnabled { | ||||
| 		api.RegisterAPIEndpoints() | ||||
| 	} | ||||
| 
 | ||||
| 	if !features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) { | ||||
| 		api.log.Warn("[Deprecated] The publicDashboards feature toggle will be removed in Grafana v11. To disable the public dashboards feature, use the public_dashboards.enabled setting.") | ||||
| 	} | ||||
| 
 | ||||
| 	return api | ||||
| } | ||||
| 
 | ||||
|  | @ -59,35 +68,35 @@ func (api *Api) RegisterAPIEndpoints() { | |||
| 	// Anonymous access to public dashboard route is configured in pkg/api/api.go
 | ||||
| 	// because it is deeply dependent on the HTTPServer.Index() method and would result in a
 | ||||
| 	// circular dependency
 | ||||
| 	api.RouteRegister.Group("/api/public/dashboards/:accessToken", func(apiRoute routing.RouteRegister) { | ||||
| 	api.routeRegister.Group("/api/public/dashboards/:accessToken", func(apiRoute routing.RouteRegister) { | ||||
| 		apiRoute.Get("/", routing.Wrap(api.ViewPublicDashboard)) | ||||
| 		apiRoute.Get("/annotations", routing.Wrap(api.GetPublicAnnotations)) | ||||
| 		apiRoute.Post("/panels/:panelId/query", routing.Wrap(api.QueryPublicDashboard)) | ||||
| 	}, api.Middleware.HandleApi) | ||||
| 
 | ||||
| 	// Auth endpoints
 | ||||
| 	auth := accesscontrol.Middleware(api.AccessControl) | ||||
| 	auth := accesscontrol.Middleware(api.accessControl) | ||||
| 	uidScope := dashboards.ScopeDashboardsProvider.GetResourceScopeUID(accesscontrol.Parameter(":dashboardUid")) | ||||
| 
 | ||||
| 	// List public dashboards for org
 | ||||
| 	api.RouteRegister.Get("/api/dashboards/public-dashboards", middleware.ReqSignedIn, routing.Wrap(api.ListPublicDashboards)) | ||||
| 	api.routeRegister.Get("/api/dashboards/public-dashboards", middleware.ReqSignedIn, routing.Wrap(api.ListPublicDashboards)) | ||||
| 	// Get public dashboard
 | ||||
| 	api.RouteRegister.Get("/api/dashboards/uid/:dashboardUid/public-dashboards", | ||||
| 	api.routeRegister.Get("/api/dashboards/uid/:dashboardUid/public-dashboards", | ||||
| 		auth(accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, uidScope)), | ||||
| 		routing.Wrap(api.GetPublicDashboard)) | ||||
| 
 | ||||
| 	// Create Public Dashboard
 | ||||
| 	api.RouteRegister.Post("/api/dashboards/uid/:dashboardUid/public-dashboards", | ||||
| 	api.routeRegister.Post("/api/dashboards/uid/:dashboardUid/public-dashboards", | ||||
| 		auth(accesscontrol.EvalPermission(dashboards.ActionDashboardsPublicWrite, uidScope)), | ||||
| 		routing.Wrap(api.CreatePublicDashboard)) | ||||
| 
 | ||||
| 	// Update Public Dashboard
 | ||||
| 	api.RouteRegister.Patch("/api/dashboards/uid/:dashboardUid/public-dashboards/:uid", | ||||
| 	api.routeRegister.Patch("/api/dashboards/uid/:dashboardUid/public-dashboards/:uid", | ||||
| 		auth(accesscontrol.EvalPermission(dashboards.ActionDashboardsPublicWrite, uidScope)), | ||||
| 		routing.Wrap(api.UpdatePublicDashboard)) | ||||
| 
 | ||||
| 	// Delete Public dashboard
 | ||||
| 	api.RouteRegister.Delete("/api/dashboards/uid/:dashboardUid/public-dashboards/:uid", | ||||
| 	api.routeRegister.Delete("/api/dashboards/uid/:dashboardUid/public-dashboards/:uid", | ||||
| 		auth(accesscontrol.EvalPermission(dashboards.ActionDashboardsPublicWrite, uidScope)), | ||||
| 		routing.Wrap(api.DeletePublicDashboard)) | ||||
| } | ||||
|  |  | |||
|  | @ -13,7 +13,6 @@ import ( | |||
| 	"github.com/stretchr/testify/require" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/org" | ||||
| 	"github.com/grafana/grafana/pkg/services/publicdashboards" | ||||
| 	. "github.com/grafana/grafana/pkg/services/publicdashboards/models" | ||||
|  | @ -27,7 +26,7 @@ var userAdmin = &user.SignedInUser{UserID: 2, OrgID: 1, OrgRole: org.RoleAdmin, | |||
| var userViewer = &user.SignedInUser{UserID: 4, OrgID: 1, OrgRole: org.RoleViewer, Login: "testViewerUserRBAC", Permissions: map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsAll}}}} | ||||
| var anonymousUser = &user.SignedInUser{IsAnonymous: true} | ||||
| 
 | ||||
| func TestAPIFeatureFlag(t *testing.T) { | ||||
| func TestAPIFeatureDisabled(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		Name   string | ||||
| 		Method string | ||||
|  | @ -71,11 +70,18 @@ func TestAPIFeatureFlag(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.Name, func(t *testing.T) { | ||||
| 		t.Run(test.Name+" - setting disabled", func(t *testing.T) { | ||||
| 			cfg := setting.NewCfg() | ||||
| 			cfg.PublicDashboardsEnabled = false | ||||
| 			service := publicdashboards.NewFakePublicDashboardService(t) | ||||
| 			features := featuremgmt.WithFeatures() | ||||
| 			testServer := setupTestServer(t, cfg, features, service, nil, userAdmin) | ||||
| 			testServer := setupTestServer(t, cfg, service, userAdmin, true) | ||||
| 			response := callAPI(testServer, test.Method, test.Path, nil, t) | ||||
| 			assert.Equal(t, http.StatusNotFound, response.Code) | ||||
| 		}) | ||||
| 
 | ||||
| 		t.Run(test.Name+" - feature flag disabled", func(t *testing.T) { | ||||
| 			service := publicdashboards.NewFakePublicDashboardService(t) | ||||
| 			testServer := setupTestServer(t, nil, service, userAdmin, false) | ||||
| 			response := callAPI(testServer, test.Method, test.Path, nil, t) | ||||
| 			assert.Equal(t, http.StatusNotFound, response.Code) | ||||
| 		}) | ||||
|  | @ -130,9 +136,7 @@ func TestAPIListPublicDashboard(t *testing.T) { | |||
| 			service.On("FindAllWithPagination", mock.Anything, mock.Anything, mock.Anything). | ||||
| 				Return(test.Response, test.ResponseErr).Maybe() | ||||
| 
 | ||||
| 			cfg := setting.NewCfg() | ||||
| 			features := featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards) | ||||
| 			testServer := setupTestServer(t, cfg, features, service, nil, test.User) | ||||
| 			testServer := setupTestServer(t, nil, service, test.User, true) | ||||
| 
 | ||||
| 			response := callAPI(testServer, http.MethodGet, "/api/dashboards/public-dashboards", nil, t) | ||||
| 			assert.Equal(t, test.ExpectedHttpResponse, response.Code) | ||||
|  | @ -259,10 +263,7 @@ func TestAPIDeletePublicDashboard(t *testing.T) { | |||
| 					Return(test.ResponseErr) | ||||
| 			} | ||||
| 
 | ||||
| 			cfg := setting.NewCfg() | ||||
| 
 | ||||
| 			features := featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards) | ||||
| 			testServer := setupTestServer(t, cfg, features, service, nil, test.User) | ||||
| 			testServer := setupTestServer(t, nil, service, test.User, true) | ||||
| 
 | ||||
| 			response := callAPI(testServer, http.MethodDelete, fmt.Sprintf("/api/dashboards/uid/%s/public-dashboards/%s", test.DashboardUid, test.PublicDashboardUid), nil, t) | ||||
| 			assert.Equal(t, test.ExpectedHttpResponse, response.Code) | ||||
|  | @ -347,16 +348,7 @@ func TestAPIGetPublicDashboard(t *testing.T) { | |||
| 					Return(test.PublicDashboardResult, test.PublicDashboardErr) | ||||
| 			} | ||||
| 
 | ||||
| 			cfg := setting.NewCfg() | ||||
| 
 | ||||
| 			testServer := setupTestServer( | ||||
| 				t, | ||||
| 				cfg, | ||||
| 				featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards), | ||||
| 				service, | ||||
| 				nil, | ||||
| 				test.User, | ||||
| 			) | ||||
| 			testServer := setupTestServer(t, nil, service, test.User, true) | ||||
| 
 | ||||
| 			response := callAPI( | ||||
| 				testServer, | ||||
|  | @ -474,16 +466,7 @@ func TestApiCreatePublicDashboard(t *testing.T) { | |||
| 					Return(&PublicDashboard{IsEnabled: true}, test.SaveDashboardErr) | ||||
| 			} | ||||
| 
 | ||||
| 			cfg := setting.NewCfg() | ||||
| 
 | ||||
| 			testServer := setupTestServer( | ||||
| 				t, | ||||
| 				cfg, | ||||
| 				featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards), | ||||
| 				service, | ||||
| 				nil, | ||||
| 				test.User, | ||||
| 			) | ||||
| 			testServer := setupTestServer(t, nil, service, test.User, true) | ||||
| 
 | ||||
| 			response := callAPI( | ||||
| 				testServer, | ||||
|  | @ -609,9 +592,6 @@ func TestAPIUpdatePublicDashboard(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| 	for _, test := range testCases { | ||||
| 		cfg := setting.NewCfg() | ||||
| 		features := featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards) | ||||
| 
 | ||||
| 		t.Run(test.Name, func(t *testing.T) { | ||||
| 			service := publicdashboards.NewFakePublicDashboardService(t) | ||||
| 
 | ||||
|  | @ -620,7 +600,7 @@ func TestAPIUpdatePublicDashboard(t *testing.T) { | |||
| 					Return(test.ExpectedResponse, test.ExpectedError) | ||||
| 			} | ||||
| 
 | ||||
| 			testServer := setupTestServer(t, cfg, features, service, nil, test.User) | ||||
| 			testServer := setupTestServer(t, nil, service, test.User, true) | ||||
| 			url := fmt.Sprintf("/api/dashboards/uid/%s/public-dashboards/%s", test.DashboardUid, test.PublicDashboardUid) | ||||
| 			body := strings.NewReader(test.Body) | ||||
| 
 | ||||
|  |  | |||
|  | @ -40,11 +40,12 @@ import ( | |||
| func setupTestServer( | ||||
| 	t *testing.T, | ||||
| 	cfg *setting.Cfg, | ||||
| 	features *featuremgmt.FeatureManager, | ||||
| 	service publicdashboards.Service, | ||||
| 	db db.DB, | ||||
| 	user *user.SignedInUser, | ||||
| 	ffEnabled bool, | ||||
| ) *web.Mux { | ||||
| 	t.Helper() | ||||
| 
 | ||||
| 	// build router to register routes
 | ||||
| 	rr := routing.NewRouteRegister() | ||||
| 
 | ||||
|  | @ -56,9 +57,18 @@ func setupTestServer( | |||
| 	// set initial context
 | ||||
| 	m.Use(contextProvider(&testContext{user})) | ||||
| 
 | ||||
| 	// build api, this will mount the routes at the same time if
 | ||||
| 	// featuremgmt.FlagPublicDashboard is enabled
 | ||||
| 	ProvideApi(service, rr, ac, features, &Middleware{}) | ||||
| 	features := featuremgmt.WithFeatures() | ||||
| 	if ffEnabled { | ||||
| 		features = featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards) | ||||
| 	} | ||||
| 
 | ||||
| 	if cfg == nil { | ||||
| 		cfg = setting.NewCfg() | ||||
| 		cfg.PublicDashboardsEnabled = true | ||||
| 	} | ||||
| 
 | ||||
| 	// build api, this will mount the routes at the same time if the feature is enabled
 | ||||
| 	ProvideApi(service, rr, ac, features, &Middleware{}, cfg) | ||||
| 
 | ||||
| 	// connect routes to mux
 | ||||
| 	rr.Register(m.Router) | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ func (api *Api) QueryPublicDashboard(c *contextmodel.ReqContext) response.Respon | |||
| 		return response.Err(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return toJsonStreamingResponse(c.Req.Context(), api.Features, resp) | ||||
| 	return toJsonStreamingResponse(c.Req.Context(), api.features, resp) | ||||
| } | ||||
| 
 | ||||
| // swagger:route GET /public/dashboards/{accessToken}/annotations dashboard_public getPublicAnnotations
 | ||||
|  |  | |||
|  | @ -101,16 +101,7 @@ func TestAPIViewPublicDashboard(t *testing.T) { | |||
| 			service.On("GetPublicDashboardForView", mock.Anything, mock.AnythingOfType("string")). | ||||
| 				Return(test.DashboardResult, test.Err).Maybe() | ||||
| 
 | ||||
| 			cfg := setting.NewCfg() | ||||
| 
 | ||||
| 			testServer := setupTestServer( | ||||
| 				t, | ||||
| 				cfg, | ||||
| 				featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards), | ||||
| 				service, | ||||
| 				nil, | ||||
| 				anonymousUser, | ||||
| 			) | ||||
| 			testServer := setupTestServer(t, nil, service, anonymousUser, true) | ||||
| 
 | ||||
| 			response := callAPI(testServer, http.MethodGet, | ||||
| 				fmt.Sprintf("/api/public/dashboards/%s", test.AccessToken), | ||||
|  | @ -202,16 +193,7 @@ func TestAPIQueryPublicDashboard(t *testing.T) { | |||
| 
 | ||||
| 	setup := func(enabled bool) (*web.Mux, *publicdashboards.FakePublicDashboardService) { | ||||
| 		service := publicdashboards.NewFakePublicDashboardService(t) | ||||
| 		cfg := setting.NewCfg() | ||||
| 
 | ||||
| 		testServer := setupTestServer( | ||||
| 			t, | ||||
| 			cfg, | ||||
| 			featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards, enabled), | ||||
| 			service, | ||||
| 			nil, | ||||
| 			anonymousUser, | ||||
| 		) | ||||
| 		testServer := setupTestServer(t, nil, service, anonymousUser, true) | ||||
| 
 | ||||
| 		return testServer, service | ||||
| 	} | ||||
|  | @ -338,6 +320,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) | |||
| 	// create public dashboard
 | ||||
| 	store := publicdashboardsStore.ProvideStore(db, db.Cfg, featuremgmt.WithFeatures()) | ||||
| 	cfg := setting.NewCfg() | ||||
| 	cfg.PublicDashboardsEnabled = true | ||||
| 	ac := acmock.New() | ||||
| 	ws := publicdashboardsService.ProvideServiceWrapper(store) | ||||
| 	folderStore := folderimpl.ProvideDashboardFolderStore(db) | ||||
|  | @ -354,13 +337,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) | |||
| 	require.NoError(t, err) | ||||
| 
 | ||||
| 	// setup test server
 | ||||
| 	server := setupTestServer(t, | ||||
| 		cfg, | ||||
| 		featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards), | ||||
| 		pds, | ||||
| 		db, | ||||
| 		anonymousUser, | ||||
| 	) | ||||
| 	server := setupTestServer(t, cfg, pds, anonymousUser, true) | ||||
| 
 | ||||
| 	resp := callAPI(server, http.MethodPost, | ||||
| 		fmt.Sprintf("/api/public/dashboards/%s/panels/1/query", pubdash.AccessToken), | ||||
|  | @ -436,7 +413,6 @@ func TestAPIGetAnnotations(t *testing.T) { | |||
| 	} | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.Name, func(t *testing.T) { | ||||
| 			cfg := setting.NewCfg() | ||||
| 			service := publicdashboards.NewFakePublicDashboardService(t) | ||||
| 
 | ||||
| 			if test.ExpectedServiceCalled { | ||||
|  | @ -444,7 +420,7 @@ func TestAPIGetAnnotations(t *testing.T) { | |||
| 					Return(test.Annotations, test.ServiceError).Once() | ||||
| 			} | ||||
| 
 | ||||
| 			testServer := setupTestServer(t, cfg, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards), service, nil, anonymousUser) | ||||
| 			testServer := setupTestServer(t, nil, service, anonymousUser, true) | ||||
| 
 | ||||
| 			path := fmt.Sprintf("/api/public/dashboards/%s/annotations?from=%s&to=%s", test.AccessToken, test.From, test.To) | ||||
| 			response := callAPI(testServer, http.MethodGet, path, nil, t) | ||||
|  |  | |||
|  | @ -58,7 +58,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) { | |||
| 	var publicdashboardStore *PublicDashboardStoreImpl | ||||
| 
 | ||||
| 	setup := func() { | ||||
| 		sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}}) | ||||
| 		sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{}) | ||||
| 		quotaService := quotatest.New(false, nil) | ||||
| 		dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) | ||||
| 		require.NoError(t, err) | ||||
|  | @ -448,7 +448,7 @@ func TestIntegrationCreatePublicDashboard(t *testing.T) { | |||
| 	var savedDashboard2 *dashboards.Dashboard | ||||
| 
 | ||||
| 	setup := func() { | ||||
| 		sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}}) | ||||
| 		sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{}) | ||||
| 		quotaService := quotatest.New(false, nil) | ||||
| 		store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) | ||||
| 		require.NoError(t, err) | ||||
|  | @ -528,7 +528,7 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) { | |||
| 	var err error | ||||
| 
 | ||||
| 	setup := func() { | ||||
| 		sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}}) | ||||
| 		sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{}) | ||||
| 		quotaService := quotatest.New(false, nil) | ||||
| 		dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) | ||||
| 		require.NoError(t, err) | ||||
|  | @ -788,7 +788,7 @@ func TestGetMetrics(t *testing.T) { | |||
| 	var savedDashboard4 *dashboards.Dashboard | ||||
| 
 | ||||
| 	setup := func() { | ||||
| 		sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}}) | ||||
| 		sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{}) | ||||
| 		quotaService := quotatest.New(false, nil) | ||||
| 		store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) | ||||
| 		require.NoError(t, err) | ||||
|  |  | |||
|  | @ -541,6 +541,9 @@ type Cfg struct { | |||
| 	// sqlstore package and HTTP middlewares.
 | ||||
| 	DatabaseInstrumentQueries bool | ||||
| 
 | ||||
| 	// Public dashboards
 | ||||
| 	PublicDashboardsEnabled bool | ||||
| 
 | ||||
| 	// Feature Management Settings
 | ||||
| 	FeatureManagement FeatureMgmtSettings | ||||
| } | ||||
|  | @ -1254,6 +1257,7 @@ func (cfg *Cfg) Load(args CommandLineArgs) error { | |||
| 	cfg.UserFacingDefaultError = logSection.Key("user_facing_default_error").MustString("please inspect Grafana server log for details") | ||||
| 
 | ||||
| 	cfg.readFeatureManagementConfig() | ||||
| 	cfg.readPublicDashboardsSettings() | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | @ -1965,3 +1969,8 @@ func (cfg *Cfg) readLiveSettings(iniFile *ini.File) error { | |||
| 	cfg.LiveAllowedOrigins = originPatterns | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (cfg *Cfg) readPublicDashboardsSettings() { | ||||
| 	publicDashboards := cfg.Raw.Section("public_dashboards") | ||||
| 	cfg.PublicDashboardsEnabled = publicDashboards.Key("enabled").MustBool(true) | ||||
| } | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; | |||
| import { config, featureEnabled } from '@grafana/runtime'; | ||||
| import { useStyles2, TabsBar, Tab } from '@grafana/ui'; | ||||
| import { contextSrv } from 'app/core/services/context_srv'; | ||||
| import { isPublicDashboardsEnabled } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils'; | ||||
| 
 | ||||
| import { Page } from '../../core/components/Page/Page'; | ||||
| import { AccessControlAction } from '../../types'; | ||||
|  | @ -46,7 +47,7 @@ export default function UserListPage() { | |||
|   const hasAccessToAdminUsers = contextSrv.hasPermission(AccessControlAction.UsersRead); | ||||
|   const hasAccessToOrgUsers = contextSrv.hasPermission(AccessControlAction.OrgUsersRead); | ||||
|   const hasEmailSharingEnabled = | ||||
|     Boolean(config.featureToggles.publicDashboards) && | ||||
|     isPublicDashboardsEnabled() && | ||||
|     Boolean(config.featureToggles.publicDashboardsEmailSharing) && | ||||
|     featureEnabled('publicDashboardsEmailSharing'); | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { SceneComponentProps, SceneObjectBase, SceneObjectState, VizPanel, Scene | |||
| import { Modal, ModalTabsHeader, TabContent } from '@grafana/ui'; | ||||
| import { contextSrv } from 'app/core/core'; | ||||
| import { t } from 'app/core/internationalization'; | ||||
| import { isPublicDashboardsEnabled } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils'; | ||||
| 
 | ||||
| import { DashboardScene } from '../scene/DashboardScene'; | ||||
| import { DashboardInteractions } from '../utils/interactions'; | ||||
|  | @ -61,7 +62,7 @@ export class ShareModal extends SceneObjectBase<ShareModalState> implements Moda | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (Boolean(config.featureToggles['publicDashboards'])) { | ||||
|     if (isPublicDashboardsEnabled()) { | ||||
|       tabs.push(new SharePublicDashboardTab({ dashboardRef, modalRef: this.getRef() })); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { config } from 'app/core/config'; | |||
| import { contextSrv } from 'app/core/core'; | ||||
| import { t } from 'app/core/internationalization'; | ||||
| import { SharePublicDashboard } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard'; | ||||
| import { isPublicDashboardsEnabled } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils'; | ||||
| import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; | ||||
| import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions'; | ||||
| import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard'; | ||||
|  | @ -56,7 +57,7 @@ function getTabs(panel?: PanelModel, activeTab?: string) { | |||
|     tabs.push(...customDashboardTabs); | ||||
|   } | ||||
| 
 | ||||
|   if (Boolean(config.featureToggles['publicDashboards'])) { | ||||
|   if (isPublicDashboardsEnabled()) { | ||||
|     tabs.push({ | ||||
|       label: 'Public dashboard', | ||||
|       value: shareDashboardType.publicDashboard, | ||||
|  |  | |||
|  | @ -12,6 +12,6 @@ export const NoUpsertPermissionsAlert = ({ mode }: { mode: 'create' | 'edit' }) | |||
|     data-testid={selectors.NoUpsertPermissionsWarningAlert} | ||||
|     bottomSpacing={0} | ||||
|   > | ||||
|     Contact your admin to get permission to {mode} create public dashboards | ||||
|     Contact your admin to get permission to {mode} public dashboards | ||||
|   </Alert> | ||||
| ); | ||||
|  |  | |||
|  | @ -70,6 +70,7 @@ beforeAll(() => { | |||
| 
 | ||||
| beforeEach(() => { | ||||
|   config.featureToggles.publicDashboards = true; | ||||
|   config.publicDashboardsEnabled = true; | ||||
| 
 | ||||
|   jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true); | ||||
|   jest.spyOn(contextSrv, 'hasRole').mockReturnValue(true); | ||||
|  | @ -137,7 +138,14 @@ describe('SharePublic', () => { | |||
|   beforeEach(() => { | ||||
|     server.use(getExistentPublicDashboardResponse()); | ||||
|   }); | ||||
|   it('does not render share panel when public dashboards feature is disabled', async () => { | ||||
|   it('does not render share panel when public dashboards feature is disabled using config setting', async () => { | ||||
|     config.publicDashboardsEnabled = false; | ||||
|     await renderSharePublicDashboard(undefined, false); | ||||
| 
 | ||||
|     expect(screen.getByRole('tablist')).toHaveTextContent('Link'); | ||||
|     expect(screen.getByRole('tablist')).not.toHaveTextContent('Public dashboard'); | ||||
|   }); | ||||
|   it('does not render share panel when public dashboards feature is disabled using feature toggle', async () => { | ||||
|     config.featureToggles.publicDashboards = false; | ||||
|     await renderSharePublicDashboard(undefined, false); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { TypedVariableModel } from '@grafana/data'; | ||||
| import { DataSourceWithBackend } from '@grafana/runtime'; | ||||
| import { config, DataSourceWithBackend } from '@grafana/runtime'; | ||||
| import { getConfig } from 'app/core/config'; | ||||
| import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; | ||||
| 
 | ||||
|  | @ -93,3 +93,7 @@ export const generatePublicDashboardConfigUrl = (dashboardUid: string, dashboard | |||
| }; | ||||
| 
 | ||||
| export const validEmailRegex = /^[A-Z\d._%+-]+@[A-Z\d.-]+\.[A-Z]{2,}$/i; | ||||
| 
 | ||||
| export const isPublicDashboardsEnabled = () => { | ||||
|   return Boolean(config.featureToggles.publicDashboards) && config.publicDashboardsEnabled; | ||||
| }; | ||||
|  |  | |||
|  | @ -4,32 +4,33 @@ import { RouteDescriptor } from '../../core/navigation/types'; | |||
| import { DashboardRoutes } from '../../types'; | ||||
| 
 | ||||
| export const getPublicDashboardRoutes = (): RouteDescriptor[] => { | ||||
|   if (config.featureToggles.publicDashboards) { | ||||
|     return [ | ||||
|       { | ||||
|         path: '/dashboard/public', | ||||
|         pageClass: 'page-dashboard', | ||||
|         routeName: DashboardRoutes.Public, | ||||
|         component: SafeDynamicImport( | ||||
|           () => | ||||
|             import( | ||||
|               /* webpackChunkName: "ListPublicDashboardPage" */ '../../features/manage-dashboards/PublicDashboardListPage' | ||||
|             ) | ||||
|         ), | ||||
|       }, | ||||
|       { | ||||
|         path: '/public-dashboards/:accessToken', | ||||
|         pageClass: 'page-dashboard', | ||||
|         routeName: DashboardRoutes.Public, | ||||
|         chromeless: true, | ||||
|         component: SafeDynamicImport( | ||||
|           () => | ||||
|             import( | ||||
|               /* webpackChunkName: "PublicDashboardPage" */ '../../features/dashboard/containers/PublicDashboardPage' | ||||
|             ) | ||||
|         ), | ||||
|       }, | ||||
|     ]; | ||||
|   if (!config.publicDashboardsEnabled || !config.featureToggles.publicDashboards) { | ||||
|     return []; | ||||
|   } | ||||
|   return []; | ||||
| 
 | ||||
|   return [ | ||||
|     { | ||||
|       path: '/dashboard/public', | ||||
|       pageClass: 'page-dashboard', | ||||
|       routeName: DashboardRoutes.Public, | ||||
|       component: SafeDynamicImport( | ||||
|         () => | ||||
|           import( | ||||
|             /* webpackChunkName: "ListPublicDashboardPage" */ '../../features/manage-dashboards/PublicDashboardListPage' | ||||
|           ) | ||||
|       ), | ||||
|     }, | ||||
|     { | ||||
|       path: '/public-dashboards/:accessToken', | ||||
|       pageClass: 'page-dashboard', | ||||
|       routeName: DashboardRoutes.Public, | ||||
|       chromeless: true, | ||||
|       component: SafeDynamicImport( | ||||
|         () => | ||||
|           import( | ||||
|             /* webpackChunkName: "PublicDashboardPage" */ '../../features/dashboard/containers/PublicDashboardPage' | ||||
|           ) | ||||
|       ), | ||||
|     }, | ||||
|   ]; | ||||
| }; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue