From 34f757ba5a970cc5fcffa92f47c5a0d41928f150 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Fri, 7 Jan 2022 15:11:23 -0500 Subject: [PATCH] switch to using featureEnabled for enterprise features (#41559) * switch to using featureEnabled for enterprise features --- packages/grafana-data/src/types/config.ts | 9 +----- packages/grafana-runtime/src/config.ts | 1 - packages/grafana-runtime/src/index.ts | 1 + .../grafana-runtime/src/utils/licensing.ts | 6 ++++ pkg/api/frontendsettings.go | 4 +-- pkg/api/index.go | 10 +------ pkg/api/login.go | 2 +- pkg/api/team_members.go | 2 +- pkg/models/licensing.go | 10 +++---- .../manager/loader/initializer/initializer.go | 4 +-- .../loader/initializer/initializer_test.go | 29 +++++++++---------- pkg/plugins/manager/loader/loader_test.go | 21 +++++++------- pkg/services/licensing/oss.go | 10 +++---- public/app/core/components/Footer/Footer.tsx | 2 +- .../ChangePasswordPage.test.tsx | 11 +++---- .../SendResetMailPage.test.tsx | 10 ++----- .../core/components/Login/LoginPage.test.tsx | 15 +++------- .../components/Signup/SignupPage.test.tsx | 17 ++++------- .../Signup/VerifyEmailPage.test.tsx | 16 ++++------ public/app/core/services/context_srv.ts | 3 +- .../backends/sentry/SentryBackend.test.ts | 1 - public/app/core/specs/OrgSwitcher.test.tsx | 9 ++---- public/app/features/admin/UserAdminPage.tsx | 4 +-- public/app/features/admin/ldap/LdapPage.tsx | 4 +-- public/app/features/admin/state/actions.ts | 6 ++-- .../datasources/state/buildCategories.ts | 6 ++-- .../features/datasources/state/navModel.ts | 7 ++++- .../Badges/PluginEnterpriseBadge.tsx | 4 +-- .../InstallControls/InstallControls.tsx | 4 +-- .../components/PluginListItemBadges.test.tsx | 4 +-- .../admin/pages/PluginDetails.test.tsx | 8 ++--- public/app/features/teams/TeamPages.test.tsx | 10 ++++--- public/app/features/teams/TeamPages.tsx | 3 +- public/app/features/teams/state/navModel.ts | 4 +-- 34 files changed, 109 insertions(+), 148 deletions(-) create mode 100644 packages/grafana-runtime/src/utils/licensing.ts diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 640281cfc90..29b3cd77db3 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -13,12 +13,6 @@ import { MapLayerOptions } from '../geo/layer'; export interface BuildInfo { version: string; commit: string; - /** - * Is set to true when running Grafana Enterprise edition. - * - * @deprecated use `licenseInfo.hasLicense` instead - */ - isEnterprise: boolean; env: string; edition: GrafanaEdition; latestVersion: string; @@ -62,12 +56,11 @@ export interface FeatureToggles { * @public */ export interface LicenseInfo { - hasLicense: boolean; expiry: number; licenseUrl: string; stateInfo: string; - hasValidLicense: boolean; edition: GrafanaEdition; + enabledFeatures: { [key: string]: boolean }; } /** diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 8d6ce2ae0d6..8f893bd384d 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -124,7 +124,6 @@ export class GrafanaBootConfig implements GrafanaConfig { version: 'v1.0', commit: '1', env: 'production', - isEnterprise: false, }, viewersCanEdit: false, editorsCanAdmin: false, diff --git a/packages/grafana-runtime/src/index.ts b/packages/grafana-runtime/src/index.ts index 1c3f648a3fb..4e2b6f35a1f 100644 --- a/packages/grafana-runtime/src/index.ts +++ b/packages/grafana-runtime/src/index.ts @@ -8,6 +8,7 @@ export * from './config'; export * from './types'; export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin'; export { reportMetaAnalytics, reportInteraction, reportPageview } from './utils/analytics'; +export { featureEnabled } from './utils/licensing'; export { logInfo, logDebug, logWarning, logError } from './utils/logging'; export { DataSourceWithBackend, diff --git a/packages/grafana-runtime/src/utils/licensing.ts b/packages/grafana-runtime/src/utils/licensing.ts new file mode 100644 index 00000000000..59f1f6f486f --- /dev/null +++ b/packages/grafana-runtime/src/utils/licensing.ts @@ -0,0 +1,6 @@ +import { config } from '../config'; + +export const featureEnabled = (feature: string): boolean => { + const { enabledFeatures } = config.licenseInfo; + return enabledFeatures && enabledFeatures[feature]; +}; diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 28461012518..49932626ddb 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -250,15 +250,13 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i "latestVersion": hs.updateChecker.LatestGrafanaVersion(), "hasUpdate": hs.updateChecker.GrafanaUpdateAvailable(), "env": setting.Env, - "isEnterprise": hs.License.HasValidLicense(), }, "licenseInfo": map[string]interface{}{ - "hasLicense": hs.License.HasLicense(), - "hasValidLicense": hs.License.HasValidLicense(), "expiry": hs.License.Expiry(), "stateInfo": hs.License.StateInfo(), "licenseUrl": hs.License.LicenseURL(hasAccess(accesscontrol.ReqGrafanaAdmin, accesscontrol.LicensingPageReaderAccess)), "edition": hs.License.Edition(), + "enabledFeatures": hs.License.EnabledFeatures(), }, "featureToggles": hs.Cfg.FeatureToggles, "rendererAvailable": hs.RenderService.IsAvailable(), diff --git a/pkg/api/index.go b/pkg/api/index.go index 85e3ef9c007..9de80ede62b 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -611,7 +611,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat NewGrafanaVersion: hs.updateChecker.LatestGrafanaVersion(), NewGrafanaVersionExists: hs.updateChecker.GrafanaUpdateAvailable(), AppName: setting.ApplicationName, - AppNameBodyClass: getAppNameBodyClass(hs.License.HasValidLicense()), + AppNameBodyClass: "app-grafana", FavIcon: "public/img/fav32.png", AppleTouchIcon: "public/img/apple-touch-icon.png", AppTitle: "Grafana", @@ -680,11 +680,3 @@ func (hs *HTTPServer) NotFoundHandler(c *models.ReqContext) { c.HTML(404, "index", data) } - -func getAppNameBodyClass(validLicense bool) string { - if validLicense { - return "app-enterprise" - } - - return "app-grafana" -} diff --git a/pkg/api/login.go b/pkg/api/login.go index 48a9ebf174b..f66a2e2740d 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -349,7 +349,7 @@ func (hs *HTTPServer) RedirectResponseWithError(ctx *models.ReqContext, err erro } func (hs *HTTPServer) samlEnabled() bool { - return hs.SettingsProvider.KeyValue("auth.saml", "enabled").MustBool(false) && hs.License.HasValidLicense() + return hs.SettingsProvider.KeyValue("auth.saml", "enabled").MustBool(false) && hs.License.FeatureEnabled("saml") } func (hs *HTTPServer) samlName() string { diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go index a71bfb14fce..bc6f7d5a68b 100644 --- a/pkg/api/team_members.go +++ b/pkg/api/team_members.go @@ -30,7 +30,7 @@ func (hs *HTTPServer) GetTeamMembers(c *models.ReqContext) response.Response { member.AvatarUrl = dtos.GetGravatarUrl(member.Email) member.Labels = []string{} - if hs.License.HasValidLicense() && member.External { + if hs.License.FeatureEnabled("teamgroupsync") && member.External { authProvider := GetAuthProviderLabel(member.AuthModule) member.Labels = append(member.Labels, authProvider) } diff --git a/pkg/models/licensing.go b/pkg/models/licensing.go index 7a5abe324a9..e49eb7a3288 100644 --- a/pkg/models/licensing.go +++ b/pkg/models/licensing.go @@ -1,12 +1,6 @@ package models type Licensing interface { - // HasValidLicense is true if a valid license exists - HasValidLicense() bool - - // HasLicense is true if there is a license provided - HasLicense() bool - // Expiry returns the unix epoch timestamp when the license expires, or 0 if no valid license is provided Expiry() int64 @@ -19,6 +13,10 @@ type Licensing interface { LicenseURL(showAdminLicensingPage bool) string StateInfo() string + + EnabledFeatures() map[string]bool + + FeatureEnabled(feature string) bool } type LicenseEnvironment interface { diff --git a/pkg/plugins/manager/loader/initializer/initializer.go b/pkg/plugins/manager/loader/initializer/initializer.go index ee7993158fc..17b065c6160 100644 --- a/pkg/plugins/manager/loader/initializer/initializer.go +++ b/pkg/plugins/manager/loader/initializer/initializer.go @@ -196,11 +196,11 @@ func (i *Initializer) envVars(plugin *plugins.Plugin) []string { fmt.Sprintf("GF_VERSION=%s", i.cfg.BuildVersion), } - if i.license != nil && i.license.HasLicense() { + if i.license != nil { hostEnv = append( hostEnv, fmt.Sprintf("GF_EDITION=%s", i.license.Edition()), - fmt.Sprintf("GF_ENTERPRISE_license_PATH=%s", i.cfg.EnterpriseLicensePath), + fmt.Sprintf("GF_ENTERPRISE_LICENSE_PATH=%s", i.cfg.EnterpriseLicensePath), ) if envProvider, ok := i.license.(models.LicenseEnvironment); ok { diff --git a/pkg/plugins/manager/loader/initializer/initializer_test.go b/pkg/plugins/manager/loader/initializer/initializer_test.go index dd1fe437529..8e66c9d6755 100644 --- a/pkg/plugins/manager/loader/initializer/initializer_test.go +++ b/pkg/plugins/manager/loader/initializer/initializer_test.go @@ -227,8 +227,8 @@ func TestInitializer_envVars(t *testing.T) { } licensing := &testLicensingService{ - edition: "test", - hasLicense: true, + edition: "test", + tokenRaw: "token", } i := &Initializer{ @@ -249,8 +249,8 @@ func TestInitializer_envVars(t *testing.T) { assert.Equal(t, "GF_PLUGIN_CUSTOM_ENV_VAR=customVal", envVars[0]) assert.Equal(t, "GF_VERSION=", envVars[1]) assert.Equal(t, "GF_EDITION=test", envVars[2]) - assert.Equal(t, "GF_ENTERPRISE_license_PATH=/path/to/ent/license", envVars[3]) - assert.Equal(t, "GF_ENTERPRISE_LICENSE_TEXT=", envVars[4]) + assert.Equal(t, "GF_ENTERPRISE_LICENSE_PATH=/path/to/ent/license", envVars[3]) + assert.Equal(t, "GF_ENTERPRISE_LICENSE_TEXT=token", envVars[4]) }) } @@ -314,13 +314,8 @@ func Test_pluginSettings_ToEnv(t *testing.T) { } type testLicensingService struct { - edition string - hasLicense bool - tokenRaw string -} - -func (t *testLicensingService) HasLicense() bool { - return t.hasLicense + edition string + tokenRaw string } func (t *testLicensingService) Expiry() int64 { @@ -343,14 +338,18 @@ func (t *testLicensingService) LicenseURL(showAdminLicensingPage bool) string { return "" } -func (t *testLicensingService) HasValidLicense() bool { - return false -} - func (t *testLicensingService) Environment() map[string]string { return map[string]string{"GF_ENTERPRISE_LICENSE_TEXT": t.tokenRaw} } +func (*testLicensingService) EnabledFeatures() map[string]bool { + return map[string]bool{} +} + +func (*testLicensingService) FeatureEnabled(feature string) bool { + return false +} + type testPlugin struct { backendplugin.Plugin } diff --git a/pkg/plugins/manager/loader/loader_test.go b/pkg/plugins/manager/loader/loader_test.go index ac137a5ed74..f1f158a9514 100644 --- a/pkg/plugins/manager/loader/loader_test.go +++ b/pkg/plugins/manager/loader/loader_test.go @@ -894,13 +894,8 @@ func newLoader(cfg *setting.Cfg) *Loader { } type fakeLicensingService struct { - edition string - hasLicense bool - tokenRaw string -} - -func (t *fakeLicensingService) HasLicense() bool { - return t.hasLicense + edition string + tokenRaw string } func (t *fakeLicensingService) Expiry() int64 { @@ -923,14 +918,18 @@ func (t *fakeLicensingService) LicenseURL(_ bool) string { return "" } -func (t *fakeLicensingService) HasValidLicense() bool { - return false -} - func (t *fakeLicensingService) Environment() map[string]string { return map[string]string{"GF_ENTERPRISE_LICENSE_TEXT": t.tokenRaw} } +func (*fakeLicensingService) EnabledFeatures() map[string]bool { + return map[string]bool{} +} + +func (*fakeLicensingService) FeatureEnabled(feature string) bool { + return false +} + type fakeLogger struct { log.Logger } diff --git a/pkg/services/licensing/oss.go b/pkg/services/licensing/oss.go index 4781554a94b..b26f99a4515 100644 --- a/pkg/services/licensing/oss.go +++ b/pkg/services/licensing/oss.go @@ -16,10 +16,6 @@ type OSSLicensingService struct { HooksService *hooks.HooksService } -func (*OSSLicensingService) HasLicense() bool { - return false -} - func (*OSSLicensingService) Expiry() int64 { return 0 } @@ -44,7 +40,11 @@ func (l *OSSLicensingService) LicenseURL(showAdminLicensingPage bool) string { return "https://grafana.com/oss/grafana?utm_source=grafana_footer" } -func (*OSSLicensingService) HasValidLicense() bool { +func (*OSSLicensingService) EnabledFeatures() map[string]bool { + return map[string]bool{} +} + +func (*OSSLicensingService) FeatureEnabled(feature string) bool { return false } diff --git a/public/app/core/components/Footer/Footer.tsx b/public/app/core/components/Footer/Footer.tsx index 9096de39106..895d0d93650 100644 --- a/public/app/core/components/Footer/Footer.tsx +++ b/public/app/core/components/Footer/Footer.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import config from 'app/core/config'; +import { config } from '@grafana/runtime'; import { Icon, IconName } from '@grafana/ui'; export interface FooterLink { diff --git a/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx b/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx index c8a92dcff3c..5021e7cf081 100644 --- a/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx @@ -10,25 +10,22 @@ jest.mock('@grafana/runtime', () => ({ getBackendSrv: () => ({ post: postMock, }), -})); - -jest.mock('app/core/config', () => { - return { + config: { loginError: false, buildInfo: { version: 'v1.0', commit: '1', env: 'production', edition: 'Open Source', - isEnterprise: false, }, licenseInfo: { stateInfo: '', licenseUrl: '', }, appSubUrl: '', - }; -}); + }, +})); + const props: Props = { ...getRouteComponentProps({ queryParams: { code: 'some code' }, diff --git a/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx b/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx index 57b5ccbc2b5..f9bb024ae40 100644 --- a/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx +++ b/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx @@ -9,24 +9,20 @@ jest.mock('@grafana/runtime', () => ({ getBackendSrv: () => ({ post: postMock, }), -})); - -jest.mock('app/core/config', () => { - return { + config: { buildInfo: { version: 'v1.0', commit: '1', env: 'production', edition: 'Open Source', - isEnterprise: false, }, licenseInfo: { stateInfo: '', licenseUrl: '', }, appSubUrl: '', - }; -}); + }, +})); describe('VerifyEmail Page', () => { it('renders correctly', () => { diff --git a/public/app/core/components/Login/LoginPage.test.tsx b/public/app/core/components/Login/LoginPage.test.tsx index 260fd09c2d7..256dcf4c6b9 100644 --- a/public/app/core/components/Login/LoginPage.test.tsx +++ b/public/app/core/components/Login/LoginPage.test.tsx @@ -8,29 +8,22 @@ jest.mock('@grafana/runtime', () => ({ getBackendSrv: () => ({ post: postMock, }), -})); - -jest.mock('app/core/config', () => { - return { + config: { loginError: false, buildInfo: { version: 'v1.0', commit: '1', env: 'production', edition: 'Open Source', - isEnterprise: false, }, licenseInfo: { stateInfo: '', licenseUrl: '', }, appSubUrl: '', - getConfig: () => ({ - appSubUrl: '', - verifyEmailEnabled: false, - }), - }; -}); + verifyEmailEnabled: false, + }, +})); describe('Login Page', () => { it('renders correctly', () => { diff --git a/public/app/core/components/Signup/SignupPage.test.tsx b/public/app/core/components/Signup/SignupPage.test.tsx index 7d99fce0601..0a480655866 100644 --- a/public/app/core/components/Signup/SignupPage.test.tsx +++ b/public/app/core/components/Signup/SignupPage.test.tsx @@ -10,30 +10,23 @@ jest.mock('@grafana/runtime', () => ({ getBackendSrv: () => ({ post: postMock, }), -})); - -jest.mock('app/core/config', () => { - return { + config: { loginError: false, buildInfo: { version: 'v1.0', commit: '1', env: 'production', edition: 'Open Source', - isEnterprise: false, }, licenseInfo: { stateInfo: '', licenseUrl: '', }, appSubUrl: '', - getConfig: () => ({ - autoAssignOrg: false, - verifyEmailEnabled: true, - appSubUrl: '', - }), - }; -}); + autoAssignOrg: false, + verifyEmailEnabled: true, + }, +})); const props = { email: '', diff --git a/public/app/core/components/Signup/VerifyEmailPage.test.tsx b/public/app/core/components/Signup/VerifyEmailPage.test.tsx index b56d09f4774..fab78b399b2 100644 --- a/public/app/core/components/Signup/VerifyEmailPage.test.tsx +++ b/public/app/core/components/Signup/VerifyEmailPage.test.tsx @@ -9,27 +9,21 @@ jest.mock('@grafana/runtime', () => ({ getBackendSrv: () => ({ post: postMock, }), -})); - -jest.mock('app/core/config', () => { - return { + config: { buildInfo: { version: 'v1.0', commit: '1', env: 'production', edition: 'Open Source', - isEnterprise: false, }, licenseInfo: { stateInfo: '', licenseUrl: '', }, - getConfig: () => ({ - verifyEmailEnabled: true, - appSubUrl: '', - }), - }; -}); + verifyEmailEnabled: true, + appSubUrl: '', + }, +})); describe('VerifyEmail Page', () => { it('renders correctly', () => { diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index 255cf24b402..f44889156d0 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -1,6 +1,7 @@ import config from '../../core/config'; import { extend } from 'lodash'; import { rangeUtil, WithAccessControlMetadata } from '@grafana/data'; +import { featureEnabled } from '@grafana/runtime'; import { AccessControlAction, UserPermission } from 'app/types'; export class User { @@ -82,7 +83,7 @@ export class ContextSrv { } accessControlEnabled(): boolean { - return config.licenseInfo.hasLicense && config.featureToggles['accesscontrol']; + return featureEnabled('accesscontrol') && config.featureToggles['accesscontrol']; } // Checks whether user has required permission diff --git a/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts b/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts index 0b6b534b6e7..8e65f28dcfa 100644 --- a/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts +++ b/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts @@ -21,7 +21,6 @@ describe('SentryEchoBackend', () => { const buildInfo: BuildInfo = { version: '1.0', commit: 'abcd123', - isEnterprise: false, env: 'production', edition: GrafanaEdition.OpenSource, latestVersion: 'ba', diff --git a/public/app/core/specs/OrgSwitcher.test.tsx b/public/app/core/specs/OrgSwitcher.test.tsx index 181b00b90ea..9ab35afa612 100644 --- a/public/app/core/specs/OrgSwitcher.test.tsx +++ b/public/app/core/specs/OrgSwitcher.test.tsx @@ -10,6 +10,9 @@ jest.mock('@grafana/runtime', () => ({ get: jest.fn().mockResolvedValue([]), post: postMock, }), + config: { + appSubUrl: '/subUrl', + }, })); jest.mock('app/core/services/context_srv', () => ({ @@ -18,12 +21,6 @@ jest.mock('app/core/services/context_srv', () => ({ }, })); -jest.mock('app/core/config', () => { - return { - appSubUrl: '/subUrl', - }; -}); - let wrapper; let orgSwitcher: OrgSwitcher; diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx index e2dac144d4a..5a4d9c789fc 100644 --- a/public/app/features/admin/UserAdminPage.tsx +++ b/public/app/features/admin/UserAdminPage.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { NavModel } from '@grafana/data'; import { getNavModel } from 'app/core/selectors/navModel'; -import config from 'app/core/config'; +import { featureEnabled } from '@grafana/runtime'; import Page from 'app/core/components/Page/Page'; import { UserProfile } from './UserProfile'; import { UserPermissions } from './UserPermissions'; @@ -119,7 +119,7 @@ export class UserAdminPage extends PureComponent { onUserEnable={this.onUserEnable} onPasswordChange={this.onPasswordChange} /> - {isLDAPUser && config.licenseInfo.hasLicense && ldapSyncInfo && canReadLDAPStatus && ( + {isLDAPUser && featureEnabled('ldapsync') && ldapSyncInfo && canReadLDAPStatus && ( )} diff --git a/public/app/features/admin/ldap/LdapPage.tsx b/public/app/features/admin/ldap/LdapPage.tsx index 900d4fa82db..de23eba550b 100644 --- a/public/app/features/admin/ldap/LdapPage.tsx +++ b/public/app/features/admin/ldap/LdapPage.tsx @@ -1,10 +1,10 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { NavModel } from '@grafana/data'; +import { featureEnabled } from '@grafana/runtime'; import { Alert, Button, LegacyForms } from '@grafana/ui'; const { FormField } = LegacyForms; import { getNavModel } from 'app/core/selectors/navModel'; -import config from 'app/core/config'; import Page from 'app/core/components/Page/Page'; import { LdapConnectionStatus } from './LdapConnectionStatus'; import { LdapSyncInfo } from './LdapSyncInfo'; @@ -99,7 +99,7 @@ export class LdapPage extends PureComponent { - {config.licenseInfo.hasLicense && ldapSyncInfo && } + {featureEnabled('ldapsync') && ldapSyncInfo && } {canReadLDAPUser && ( <> diff --git a/public/app/features/admin/state/actions.ts b/public/app/features/admin/state/actions.ts index ff5aaa23d1e..b3b60b8d2e2 100644 --- a/public/app/features/admin/state/actions.ts +++ b/public/app/features/admin/state/actions.ts @@ -1,6 +1,6 @@ import config from 'app/core/config'; import { dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data'; -import { getBackendSrv, locationService } from '@grafana/runtime'; +import { featureEnabled, getBackendSrv, locationService } from '@grafana/runtime'; import { ThunkResult, LdapUser, UserSession, UserDTO, AccessControlAction, UserFilter } from 'app/types'; import { @@ -35,7 +35,7 @@ export function loadAdminUserPage(userId: number): ThunkResult { await dispatch(loadUserProfile(userId)); await dispatch(loadUserOrgs(userId)); await dispatch(loadUserSessions(userId)); - if (config.ldapEnabled && config.licenseInfo.hasLicense) { + if (config.ldapEnabled && featureEnabled('ldapsync')) { await dispatch(loadLdapSyncStatus()); } dispatch(userAdminPageLoadedAction(true)); @@ -183,7 +183,7 @@ export function loadLdapSyncStatus(): ThunkResult { return async (dispatch) => { // Available only in enterprise const canReadLDAPStatus = contextSrv.hasPermission(AccessControlAction.LDAPStatusRead); - if (config.licenseInfo.hasLicense && canReadLDAPStatus) { + if (featureEnabled('ldapsync') && canReadLDAPStatus) { const syncStatus = await getBackendSrv().get(`/api/admin/ldap-sync-status`); dispatch(ldapSyncStatusLoadedAction(syncStatus)); } diff --git a/public/app/features/datasources/state/buildCategories.ts b/public/app/features/datasources/state/buildCategories.ts index 4969c07f932..bacfc35661c 100644 --- a/public/app/features/datasources/state/buildCategories.ts +++ b/public/app/features/datasources/state/buildCategories.ts @@ -1,6 +1,6 @@ import { DataSourcePluginMeta, PluginType } from '@grafana/data'; +import { config, featureEnabled } from '@grafana/runtime'; import { DataSourcePluginCategory } from 'app/types'; -import { config } from '../../../core/config'; export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePluginCategory[] { const categories: DataSourcePluginCategory[] = [ @@ -23,14 +23,12 @@ export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePlug categoryIndex[category.id] = category; } - const { edition, hasValidLicense } = config.licenseInfo; - for (const plugin of plugins) { const enterprisePlugin = enterprisePlugins.find((item) => item.id === plugin.id); // Force category for enterprise plugins if (plugin.enterprise || enterprisePlugin) { plugin.category = 'enterprise'; - plugin.unlicensed = edition !== 'Open Source' && !hasValidLicense; + plugin.unlicensed = !featureEnabled('enterprise.plugins'); plugin.info.links = enterprisePlugin?.info?.links || plugin.info.links; } diff --git a/public/app/features/datasources/state/navModel.ts b/public/app/features/datasources/state/navModel.ts index 382d8643871..b75dfad3d91 100644 --- a/public/app/features/datasources/state/navModel.ts +++ b/public/app/features/datasources/state/navModel.ts @@ -1,4 +1,5 @@ import { DataSourceSettings, PluginType, PluginInclude, NavModel, NavModelItem } from '@grafana/data'; +import { featureEnabled } from '@grafana/runtime'; import config from 'app/core/config'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; @@ -47,7 +48,7 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat }); } - if (config.licenseInfo.hasLicense) { + if (featureEnabled('dspermissions')) { if (contextSrv.hasPermission(AccessControlAction.DataSourcesPermissionsRead)) { navModel.children!.push({ active: false, @@ -57,7 +58,9 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat url: `datasources/edit/${dataSource.id}/permissions`, }); } + } + if (featureEnabled('analytics')) { navModel.children!.push({ active: false, icon: 'info-circle', @@ -65,7 +68,9 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat text: 'Insights', url: `datasources/edit/${dataSource.id}/insights`, }); + } + if (featureEnabled('caching')) { navModel.children!.push({ active: false, icon: 'database', diff --git a/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx index 2843e4a75c4..345e077ba5f 100644 --- a/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx +++ b/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Badge, Button, HorizontalGroup, PluginSignatureBadge, useStyles2 } from '@grafana/ui'; import { CatalogPlugin } from '../../types'; import { getBadgeColor } from './sharedStyles'; -import { config } from '@grafana/runtime'; +import { featureEnabled } from '@grafana/runtime'; type Props = { plugin: CatalogPlugin }; @@ -17,7 +17,7 @@ export function PluginEnterpriseBadge({ plugin }: Props): React.ReactElement { ); }; - if (config.licenseInfo?.hasValidLicense) { + if (featureEnabled('enterprise.plugins')) { return ; } diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx index aad623da4ab..17cafd20c43 100644 --- a/public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { css } from '@emotion/css'; -import { config } from '@grafana/runtime'; +import { config, featureEnabled } from '@grafana/runtime'; import { HorizontalGroup, Icon, LinkButton, useStyles2 } from '@grafana/ui'; import { GrafanaTheme2, PluginType } from '@grafana/data'; @@ -39,7 +39,7 @@ export const InstallControls = ({ plugin, latestCompatibleVersion }: Props) => { return
Renderer plugins cannot be managed by the Plugin Catalog.
; } - if (plugin.isEnterprise && !config.licenseInfo?.hasValidLicense) { + if (plugin.isEnterprise && !featureEnabled('enterprise.plugins')) { return ( No valid Grafana Enterprise license detected. diff --git a/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx b/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx index 0c933680fc4..b455f9bd42a 100644 --- a/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx +++ b/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx @@ -49,14 +49,14 @@ describe('PluginListItemBadges', () => { }); it('renders an enterprise badge (when a license is valid)', () => { - config.licenseInfo.hasValidLicense = true; + config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true }; render(); expect(screen.getByText(/enterprise/i)).toBeVisible(); expect(screen.queryByRole('button', { name: /learn more/i })).not.toBeInTheDocument(); }); it('renders an enterprise badge with icon and link (when a license is invalid)', () => { - config.licenseInfo.hasValidLicense = false; + config.licenseInfo.enabledFeatures = {}; render(); expect(screen.getByText(/enterprise/i)).toBeVisible(); expect(screen.getByLabelText(/lock icon/i)).toBeInTheDocument(); diff --git a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx index 2057a28ca97..aaea83fea93 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx @@ -90,7 +90,7 @@ describe('Plugin details page', () => { afterEach(() => { jest.clearAllMocks(); config.pluginAdminExternalManageEnabled = false; - config.licenseInfo.hasValidLicense = false; + config.licenseInfo.enabledFeatures = {}; }); afterAll(() => { @@ -325,7 +325,7 @@ describe('Plugin details page', () => { }); it('should display an install button for enterprise plugins if license is valid', async () => { - config.licenseInfo.hasValidLicense = true; + config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true }; const { queryByRole } = renderPluginDetails({ id, isInstalled: false, isEnterprise: true }); @@ -333,7 +333,7 @@ describe('Plugin details page', () => { }); it('should not display install button for enterprise plugins if license is invalid', async () => { - config.licenseInfo.hasValidLicense = false; + config.licenseInfo.enabledFeatures = {}; const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: true, isEnterprise: true }); @@ -772,7 +772,7 @@ describe('Plugin details page', () => { }); it('should not display an install button for enterprise plugins if license is valid', async () => { - config.licenseInfo.hasValidLicense = true; + config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true }; const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: false, isEnterprise: true }); await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument()); diff --git a/public/app/features/teams/TeamPages.test.tsx b/public/app/features/teams/TeamPages.test.tsx index 88cf3cb4950..6497b77e6de 100644 --- a/public/app/features/teams/TeamPages.test.tsx +++ b/public/app/features/teams/TeamPages.test.tsx @@ -7,10 +7,12 @@ import { User } from 'app/core/services/context_srv'; import { NavModel } from '@grafana/data'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; -jest.mock('app/core/config', () => ({ - ...((jest.requireActual('app/core/config') as unknown) as object), - licenseInfo: { - hasLicense: true, +jest.mock('@grafana/runtime/src/config', () => ({ + ...((jest.requireActual('@grafana/runtime/src/config') as unknown) as object), + config: { + licenseInfo: { + enabledFeatures: { teamsync: true }, + }, }, })); diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index 9757d98d3bf..bf678eaa32e 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -13,6 +13,7 @@ import { getTeamLoadingNav } from './state/navModel'; import { getNavModel } from 'app/core/selectors/navModel'; import { contextSrv } from 'app/core/services/context_srv'; import { NavModel } from '@grafana/data'; +import { featureEnabled } from '@grafana/runtime'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; interface TeamPageRouteParams { @@ -67,7 +68,7 @@ export class TeamPages extends PureComponent { this.state = { isLoading: false, - isSyncEnabled: config.licenseInfo.hasLicense, + isSyncEnabled: featureEnabled('teamsync'), }; } diff --git a/public/app/features/teams/state/navModel.ts b/public/app/features/teams/state/navModel.ts index 1868162af0a..0e880e49022 100644 --- a/public/app/features/teams/state/navModel.ts +++ b/public/app/features/teams/state/navModel.ts @@ -1,5 +1,5 @@ import { Team, TeamPermissionLevel } from 'app/types'; -import config from 'app/core/config'; +import { featureEnabled } from '@grafana/runtime'; import { NavModelItem, NavModel } from '@grafana/data'; export function buildNavModel(team: Team): NavModelItem { @@ -28,7 +28,7 @@ export function buildNavModel(team: Team): NavModelItem { ], }; - if (config.licenseInfo.hasLicense) { + if (featureEnabled('teamsync')) { navModel.children.push({ active: false, icon: 'sync',