switch to using featureEnabled for enterprise features (#41559)

* switch to using featureEnabled for enterprise features
pull/43825/head
Dan Cech 4 years ago committed by GitHub
parent 9eb82f9fff
commit 34f757ba5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      packages/grafana-data/src/types/config.ts
  2. 1
      packages/grafana-runtime/src/config.ts
  3. 1
      packages/grafana-runtime/src/index.ts
  4. 6
      packages/grafana-runtime/src/utils/licensing.ts
  5. 4
      pkg/api/frontendsettings.go
  6. 10
      pkg/api/index.go
  7. 2
      pkg/api/login.go
  8. 2
      pkg/api/team_members.go
  9. 10
      pkg/models/licensing.go
  10. 4
      pkg/plugins/manager/loader/initializer/initializer.go
  11. 23
      pkg/plugins/manager/loader/initializer/initializer_test.go
  12. 17
      pkg/plugins/manager/loader/loader_test.go
  13. 10
      pkg/services/licensing/oss.go
  14. 2
      public/app/core/components/Footer/Footer.tsx
  15. 11
      public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx
  16. 10
      public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx
  17. 13
      public/app/core/components/Login/LoginPage.test.tsx
  18. 13
      public/app/core/components/Signup/SignupPage.test.tsx
  19. 12
      public/app/core/components/Signup/VerifyEmailPage.test.tsx
  20. 3
      public/app/core/services/context_srv.ts
  21. 1
      public/app/core/services/echo/backends/sentry/SentryBackend.test.ts
  22. 9
      public/app/core/specs/OrgSwitcher.test.tsx
  23. 4
      public/app/features/admin/UserAdminPage.tsx
  24. 4
      public/app/features/admin/ldap/LdapPage.tsx
  25. 6
      public/app/features/admin/state/actions.ts
  26. 6
      public/app/features/datasources/state/buildCategories.ts
  27. 7
      public/app/features/datasources/state/navModel.ts
  28. 4
      public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx
  29. 4
      public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx
  30. 4
      public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx
  31. 8
      public/app/features/plugins/admin/pages/PluginDetails.test.tsx
  32. 8
      public/app/features/teams/TeamPages.test.tsx
  33. 3
      public/app/features/teams/TeamPages.tsx
  34. 4
      public/app/features/teams/state/navModel.ts

@ -13,12 +13,6 @@ import { MapLayerOptions } from '../geo/layer';
export interface BuildInfo { export interface BuildInfo {
version: string; version: string;
commit: string; commit: string;
/**
* Is set to true when running Grafana Enterprise edition.
*
* @deprecated use `licenseInfo.hasLicense` instead
*/
isEnterprise: boolean;
env: string; env: string;
edition: GrafanaEdition; edition: GrafanaEdition;
latestVersion: string; latestVersion: string;
@ -62,12 +56,11 @@ export interface FeatureToggles {
* @public * @public
*/ */
export interface LicenseInfo { export interface LicenseInfo {
hasLicense: boolean;
expiry: number; expiry: number;
licenseUrl: string; licenseUrl: string;
stateInfo: string; stateInfo: string;
hasValidLicense: boolean;
edition: GrafanaEdition; edition: GrafanaEdition;
enabledFeatures: { [key: string]: boolean };
} }
/** /**

@ -124,7 +124,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
version: 'v1.0', version: 'v1.0',
commit: '1', commit: '1',
env: 'production', env: 'production',
isEnterprise: false,
}, },
viewersCanEdit: false, viewersCanEdit: false,
editorsCanAdmin: false, editorsCanAdmin: false,

@ -8,6 +8,7 @@ export * from './config';
export * from './types'; export * from './types';
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin'; export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
export { reportMetaAnalytics, reportInteraction, reportPageview } from './utils/analytics'; export { reportMetaAnalytics, reportInteraction, reportPageview } from './utils/analytics';
export { featureEnabled } from './utils/licensing';
export { logInfo, logDebug, logWarning, logError } from './utils/logging'; export { logInfo, logDebug, logWarning, logError } from './utils/logging';
export { export {
DataSourceWithBackend, DataSourceWithBackend,

@ -0,0 +1,6 @@
import { config } from '../config';
export const featureEnabled = (feature: string): boolean => {
const { enabledFeatures } = config.licenseInfo;
return enabledFeatures && enabledFeatures[feature];
};

@ -250,15 +250,13 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
"latestVersion": hs.updateChecker.LatestGrafanaVersion(), "latestVersion": hs.updateChecker.LatestGrafanaVersion(),
"hasUpdate": hs.updateChecker.GrafanaUpdateAvailable(), "hasUpdate": hs.updateChecker.GrafanaUpdateAvailable(),
"env": setting.Env, "env": setting.Env,
"isEnterprise": hs.License.HasValidLicense(),
}, },
"licenseInfo": map[string]interface{}{ "licenseInfo": map[string]interface{}{
"hasLicense": hs.License.HasLicense(),
"hasValidLicense": hs.License.HasValidLicense(),
"expiry": hs.License.Expiry(), "expiry": hs.License.Expiry(),
"stateInfo": hs.License.StateInfo(), "stateInfo": hs.License.StateInfo(),
"licenseUrl": hs.License.LicenseURL(hasAccess(accesscontrol.ReqGrafanaAdmin, accesscontrol.LicensingPageReaderAccess)), "licenseUrl": hs.License.LicenseURL(hasAccess(accesscontrol.ReqGrafanaAdmin, accesscontrol.LicensingPageReaderAccess)),
"edition": hs.License.Edition(), "edition": hs.License.Edition(),
"enabledFeatures": hs.License.EnabledFeatures(),
}, },
"featureToggles": hs.Cfg.FeatureToggles, "featureToggles": hs.Cfg.FeatureToggles,
"rendererAvailable": hs.RenderService.IsAvailable(), "rendererAvailable": hs.RenderService.IsAvailable(),

@ -611,7 +611,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
NewGrafanaVersion: hs.updateChecker.LatestGrafanaVersion(), NewGrafanaVersion: hs.updateChecker.LatestGrafanaVersion(),
NewGrafanaVersionExists: hs.updateChecker.GrafanaUpdateAvailable(), NewGrafanaVersionExists: hs.updateChecker.GrafanaUpdateAvailable(),
AppName: setting.ApplicationName, AppName: setting.ApplicationName,
AppNameBodyClass: getAppNameBodyClass(hs.License.HasValidLicense()), AppNameBodyClass: "app-grafana",
FavIcon: "public/img/fav32.png", FavIcon: "public/img/fav32.png",
AppleTouchIcon: "public/img/apple-touch-icon.png", AppleTouchIcon: "public/img/apple-touch-icon.png",
AppTitle: "Grafana", AppTitle: "Grafana",
@ -680,11 +680,3 @@ func (hs *HTTPServer) NotFoundHandler(c *models.ReqContext) {
c.HTML(404, "index", data) c.HTML(404, "index", data)
} }
func getAppNameBodyClass(validLicense bool) string {
if validLicense {
return "app-enterprise"
}
return "app-grafana"
}

@ -349,7 +349,7 @@ func (hs *HTTPServer) RedirectResponseWithError(ctx *models.ReqContext, err erro
} }
func (hs *HTTPServer) samlEnabled() bool { 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 { func (hs *HTTPServer) samlName() string {

@ -30,7 +30,7 @@ func (hs *HTTPServer) GetTeamMembers(c *models.ReqContext) response.Response {
member.AvatarUrl = dtos.GetGravatarUrl(member.Email) member.AvatarUrl = dtos.GetGravatarUrl(member.Email)
member.Labels = []string{} member.Labels = []string{}
if hs.License.HasValidLicense() && member.External { if hs.License.FeatureEnabled("teamgroupsync") && member.External {
authProvider := GetAuthProviderLabel(member.AuthModule) authProvider := GetAuthProviderLabel(member.AuthModule)
member.Labels = append(member.Labels, authProvider) member.Labels = append(member.Labels, authProvider)
} }

@ -1,12 +1,6 @@
package models package models
type Licensing interface { 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 returns the unix epoch timestamp when the license expires, or 0 if no valid license is provided
Expiry() int64 Expiry() int64
@ -19,6 +13,10 @@ type Licensing interface {
LicenseURL(showAdminLicensingPage bool) string LicenseURL(showAdminLicensingPage bool) string
StateInfo() string StateInfo() string
EnabledFeatures() map[string]bool
FeatureEnabled(feature string) bool
} }
type LicenseEnvironment interface { type LicenseEnvironment interface {

@ -196,11 +196,11 @@ func (i *Initializer) envVars(plugin *plugins.Plugin) []string {
fmt.Sprintf("GF_VERSION=%s", i.cfg.BuildVersion), fmt.Sprintf("GF_VERSION=%s", i.cfg.BuildVersion),
} }
if i.license != nil && i.license.HasLicense() { if i.license != nil {
hostEnv = append( hostEnv = append(
hostEnv, hostEnv,
fmt.Sprintf("GF_EDITION=%s", i.license.Edition()), 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 { if envProvider, ok := i.license.(models.LicenseEnvironment); ok {

@ -228,7 +228,7 @@ func TestInitializer_envVars(t *testing.T) {
licensing := &testLicensingService{ licensing := &testLicensingService{
edition: "test", edition: "test",
hasLicense: true, tokenRaw: "token",
} }
i := &Initializer{ 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_PLUGIN_CUSTOM_ENV_VAR=customVal", envVars[0])
assert.Equal(t, "GF_VERSION=", envVars[1]) assert.Equal(t, "GF_VERSION=", envVars[1])
assert.Equal(t, "GF_EDITION=test", envVars[2]) 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_PATH=/path/to/ent/license", envVars[3])
assert.Equal(t, "GF_ENTERPRISE_LICENSE_TEXT=", envVars[4]) assert.Equal(t, "GF_ENTERPRISE_LICENSE_TEXT=token", envVars[4])
}) })
} }
@ -315,14 +315,9 @@ func Test_pluginSettings_ToEnv(t *testing.T) {
type testLicensingService struct { type testLicensingService struct {
edition string edition string
hasLicense bool
tokenRaw string tokenRaw string
} }
func (t *testLicensingService) HasLicense() bool {
return t.hasLicense
}
func (t *testLicensingService) Expiry() int64 { func (t *testLicensingService) Expiry() int64 {
return 0 return 0
} }
@ -343,14 +338,18 @@ func (t *testLicensingService) LicenseURL(showAdminLicensingPage bool) string {
return "" return ""
} }
func (t *testLicensingService) HasValidLicense() bool {
return false
}
func (t *testLicensingService) Environment() map[string]string { func (t *testLicensingService) Environment() map[string]string {
return map[string]string{"GF_ENTERPRISE_LICENSE_TEXT": t.tokenRaw} 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 { type testPlugin struct {
backendplugin.Plugin backendplugin.Plugin
} }

@ -895,14 +895,9 @@ func newLoader(cfg *setting.Cfg) *Loader {
type fakeLicensingService struct { type fakeLicensingService struct {
edition string edition string
hasLicense bool
tokenRaw string tokenRaw string
} }
func (t *fakeLicensingService) HasLicense() bool {
return t.hasLicense
}
func (t *fakeLicensingService) Expiry() int64 { func (t *fakeLicensingService) Expiry() int64 {
return 0 return 0
} }
@ -923,14 +918,18 @@ func (t *fakeLicensingService) LicenseURL(_ bool) string {
return "" return ""
} }
func (t *fakeLicensingService) HasValidLicense() bool {
return false
}
func (t *fakeLicensingService) Environment() map[string]string { func (t *fakeLicensingService) Environment() map[string]string {
return map[string]string{"GF_ENTERPRISE_LICENSE_TEXT": t.tokenRaw} 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 { type fakeLogger struct {
log.Logger log.Logger
} }

@ -16,10 +16,6 @@ type OSSLicensingService struct {
HooksService *hooks.HooksService HooksService *hooks.HooksService
} }
func (*OSSLicensingService) HasLicense() bool {
return false
}
func (*OSSLicensingService) Expiry() int64 { func (*OSSLicensingService) Expiry() int64 {
return 0 return 0
} }
@ -44,7 +40,11 @@ func (l *OSSLicensingService) LicenseURL(showAdminLicensingPage bool) string {
return "https://grafana.com/oss/grafana?utm_source=grafana_footer" 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 return false
} }

@ -1,5 +1,5 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import config from 'app/core/config'; import { config } from '@grafana/runtime';
import { Icon, IconName } from '@grafana/ui'; import { Icon, IconName } from '@grafana/ui';
export interface FooterLink { export interface FooterLink {

@ -10,25 +10,22 @@ jest.mock('@grafana/runtime', () => ({
getBackendSrv: () => ({ getBackendSrv: () => ({
post: postMock, post: postMock,
}), }),
})); config: {
jest.mock('app/core/config', () => {
return {
loginError: false, loginError: false,
buildInfo: { buildInfo: {
version: 'v1.0', version: 'v1.0',
commit: '1', commit: '1',
env: 'production', env: 'production',
edition: 'Open Source', edition: 'Open Source',
isEnterprise: false,
}, },
licenseInfo: { licenseInfo: {
stateInfo: '', stateInfo: '',
licenseUrl: '', licenseUrl: '',
}, },
appSubUrl: '', appSubUrl: '',
}; },
}); }));
const props: Props = { const props: Props = {
...getRouteComponentProps({ ...getRouteComponentProps({
queryParams: { code: 'some code' }, queryParams: { code: 'some code' },

@ -9,24 +9,20 @@ jest.mock('@grafana/runtime', () => ({
getBackendSrv: () => ({ getBackendSrv: () => ({
post: postMock, post: postMock,
}), }),
})); config: {
jest.mock('app/core/config', () => {
return {
buildInfo: { buildInfo: {
version: 'v1.0', version: 'v1.0',
commit: '1', commit: '1',
env: 'production', env: 'production',
edition: 'Open Source', edition: 'Open Source',
isEnterprise: false,
}, },
licenseInfo: { licenseInfo: {
stateInfo: '', stateInfo: '',
licenseUrl: '', licenseUrl: '',
}, },
appSubUrl: '', appSubUrl: '',
}; },
}); }));
describe('VerifyEmail Page', () => { describe('VerifyEmail Page', () => {
it('renders correctly', () => { it('renders correctly', () => {

@ -8,29 +8,22 @@ jest.mock('@grafana/runtime', () => ({
getBackendSrv: () => ({ getBackendSrv: () => ({
post: postMock, post: postMock,
}), }),
})); config: {
jest.mock('app/core/config', () => {
return {
loginError: false, loginError: false,
buildInfo: { buildInfo: {
version: 'v1.0', version: 'v1.0',
commit: '1', commit: '1',
env: 'production', env: 'production',
edition: 'Open Source', edition: 'Open Source',
isEnterprise: false,
}, },
licenseInfo: { licenseInfo: {
stateInfo: '', stateInfo: '',
licenseUrl: '', licenseUrl: '',
}, },
appSubUrl: '',
getConfig: () => ({
appSubUrl: '', appSubUrl: '',
verifyEmailEnabled: false, verifyEmailEnabled: false,
}), },
}; }));
});
describe('Login Page', () => { describe('Login Page', () => {
it('renders correctly', () => { it('renders correctly', () => {

@ -10,30 +10,23 @@ jest.mock('@grafana/runtime', () => ({
getBackendSrv: () => ({ getBackendSrv: () => ({
post: postMock, post: postMock,
}), }),
})); config: {
jest.mock('app/core/config', () => {
return {
loginError: false, loginError: false,
buildInfo: { buildInfo: {
version: 'v1.0', version: 'v1.0',
commit: '1', commit: '1',
env: 'production', env: 'production',
edition: 'Open Source', edition: 'Open Source',
isEnterprise: false,
}, },
licenseInfo: { licenseInfo: {
stateInfo: '', stateInfo: '',
licenseUrl: '', licenseUrl: '',
}, },
appSubUrl: '', appSubUrl: '',
getConfig: () => ({
autoAssignOrg: false, autoAssignOrg: false,
verifyEmailEnabled: true, verifyEmailEnabled: true,
appSubUrl: '', },
}), }));
};
});
const props = { const props = {
email: '', email: '',

@ -9,27 +9,21 @@ jest.mock('@grafana/runtime', () => ({
getBackendSrv: () => ({ getBackendSrv: () => ({
post: postMock, post: postMock,
}), }),
})); config: {
jest.mock('app/core/config', () => {
return {
buildInfo: { buildInfo: {
version: 'v1.0', version: 'v1.0',
commit: '1', commit: '1',
env: 'production', env: 'production',
edition: 'Open Source', edition: 'Open Source',
isEnterprise: false,
}, },
licenseInfo: { licenseInfo: {
stateInfo: '', stateInfo: '',
licenseUrl: '', licenseUrl: '',
}, },
getConfig: () => ({
verifyEmailEnabled: true, verifyEmailEnabled: true,
appSubUrl: '', appSubUrl: '',
}), },
}; }));
});
describe('VerifyEmail Page', () => { describe('VerifyEmail Page', () => {
it('renders correctly', () => { it('renders correctly', () => {

@ -1,6 +1,7 @@
import config from '../../core/config'; import config from '../../core/config';
import { extend } from 'lodash'; import { extend } from 'lodash';
import { rangeUtil, WithAccessControlMetadata } from '@grafana/data'; import { rangeUtil, WithAccessControlMetadata } from '@grafana/data';
import { featureEnabled } from '@grafana/runtime';
import { AccessControlAction, UserPermission } from 'app/types'; import { AccessControlAction, UserPermission } from 'app/types';
export class User { export class User {
@ -82,7 +83,7 @@ export class ContextSrv {
} }
accessControlEnabled(): boolean { accessControlEnabled(): boolean {
return config.licenseInfo.hasLicense && config.featureToggles['accesscontrol']; return featureEnabled('accesscontrol') && config.featureToggles['accesscontrol'];
} }
// Checks whether user has required permission // Checks whether user has required permission

@ -21,7 +21,6 @@ describe('SentryEchoBackend', () => {
const buildInfo: BuildInfo = { const buildInfo: BuildInfo = {
version: '1.0', version: '1.0',
commit: 'abcd123', commit: 'abcd123',
isEnterprise: false,
env: 'production', env: 'production',
edition: GrafanaEdition.OpenSource, edition: GrafanaEdition.OpenSource,
latestVersion: 'ba', latestVersion: 'ba',

@ -10,6 +10,9 @@ jest.mock('@grafana/runtime', () => ({
get: jest.fn().mockResolvedValue([]), get: jest.fn().mockResolvedValue([]),
post: postMock, post: postMock,
}), }),
config: {
appSubUrl: '/subUrl',
},
})); }));
jest.mock('app/core/services/context_srv', () => ({ 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 wrapper;
let orgSwitcher: OrgSwitcher; let orgSwitcher: OrgSwitcher;

@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux'; import { connect, ConnectedProps } from 'react-redux';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { getNavModel } from 'app/core/selectors/navModel'; 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 Page from 'app/core/components/Page/Page';
import { UserProfile } from './UserProfile'; import { UserProfile } from './UserProfile';
import { UserPermissions } from './UserPermissions'; import { UserPermissions } from './UserPermissions';
@ -119,7 +119,7 @@ export class UserAdminPage extends PureComponent<Props> {
onUserEnable={this.onUserEnable} onUserEnable={this.onUserEnable}
onPasswordChange={this.onPasswordChange} onPasswordChange={this.onPasswordChange}
/> />
{isLDAPUser && config.licenseInfo.hasLicense && ldapSyncInfo && canReadLDAPStatus && ( {isLDAPUser && featureEnabled('ldapsync') && ldapSyncInfo && canReadLDAPStatus && (
<UserLdapSyncInfo ldapSyncInfo={ldapSyncInfo} user={user} onUserSync={this.onUserSync} /> <UserLdapSyncInfo ldapSyncInfo={ldapSyncInfo} user={user} onUserSync={this.onUserSync} />
)} )}
<UserPermissions isGrafanaAdmin={user.isGrafanaAdmin} onGrafanaAdminChange={this.onGrafanaAdminChange} /> <UserPermissions isGrafanaAdmin={user.isGrafanaAdmin} onGrafanaAdminChange={this.onGrafanaAdminChange} />

@ -1,10 +1,10 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux'; import { connect, ConnectedProps } from 'react-redux';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { featureEnabled } from '@grafana/runtime';
import { Alert, Button, LegacyForms } from '@grafana/ui'; import { Alert, Button, LegacyForms } from '@grafana/ui';
const { FormField } = LegacyForms; const { FormField } = LegacyForms;
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
import config from 'app/core/config';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { LdapConnectionStatus } from './LdapConnectionStatus'; import { LdapConnectionStatus } from './LdapConnectionStatus';
import { LdapSyncInfo } from './LdapSyncInfo'; import { LdapSyncInfo } from './LdapSyncInfo';
@ -99,7 +99,7 @@ export class LdapPage extends PureComponent<Props, State> {
<LdapConnectionStatus ldapConnectionInfo={ldapConnectionInfo} /> <LdapConnectionStatus ldapConnectionInfo={ldapConnectionInfo} />
{config.licenseInfo.hasLicense && ldapSyncInfo && <LdapSyncInfo ldapSyncInfo={ldapSyncInfo} />} {featureEnabled('ldapsync') && ldapSyncInfo && <LdapSyncInfo ldapSyncInfo={ldapSyncInfo} />}
{canReadLDAPUser && ( {canReadLDAPUser && (
<> <>

@ -1,6 +1,6 @@
import config from 'app/core/config'; import config from 'app/core/config';
import { dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data'; 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 { ThunkResult, LdapUser, UserSession, UserDTO, AccessControlAction, UserFilter } from 'app/types';
import { import {
@ -35,7 +35,7 @@ export function loadAdminUserPage(userId: number): ThunkResult<void> {
await dispatch(loadUserProfile(userId)); await dispatch(loadUserProfile(userId));
await dispatch(loadUserOrgs(userId)); await dispatch(loadUserOrgs(userId));
await dispatch(loadUserSessions(userId)); await dispatch(loadUserSessions(userId));
if (config.ldapEnabled && config.licenseInfo.hasLicense) { if (config.ldapEnabled && featureEnabled('ldapsync')) {
await dispatch(loadLdapSyncStatus()); await dispatch(loadLdapSyncStatus());
} }
dispatch(userAdminPageLoadedAction(true)); dispatch(userAdminPageLoadedAction(true));
@ -183,7 +183,7 @@ export function loadLdapSyncStatus(): ThunkResult<void> {
return async (dispatch) => { return async (dispatch) => {
// Available only in enterprise // Available only in enterprise
const canReadLDAPStatus = contextSrv.hasPermission(AccessControlAction.LDAPStatusRead); 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`); const syncStatus = await getBackendSrv().get(`/api/admin/ldap-sync-status`);
dispatch(ldapSyncStatusLoadedAction(syncStatus)); dispatch(ldapSyncStatusLoadedAction(syncStatus));
} }

@ -1,6 +1,6 @@
import { DataSourcePluginMeta, PluginType } from '@grafana/data'; import { DataSourcePluginMeta, PluginType } from '@grafana/data';
import { config, featureEnabled } from '@grafana/runtime';
import { DataSourcePluginCategory } from 'app/types'; import { DataSourcePluginCategory } from 'app/types';
import { config } from '../../../core/config';
export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePluginCategory[] { export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePluginCategory[] {
const categories: DataSourcePluginCategory[] = [ const categories: DataSourcePluginCategory[] = [
@ -23,14 +23,12 @@ export function buildCategories(plugins: DataSourcePluginMeta[]): DataSourcePlug
categoryIndex[category.id] = category; categoryIndex[category.id] = category;
} }
const { edition, hasValidLicense } = config.licenseInfo;
for (const plugin of plugins) { for (const plugin of plugins) {
const enterprisePlugin = enterprisePlugins.find((item) => item.id === plugin.id); const enterprisePlugin = enterprisePlugins.find((item) => item.id === plugin.id);
// Force category for enterprise plugins // Force category for enterprise plugins
if (plugin.enterprise || enterprisePlugin) { if (plugin.enterprise || enterprisePlugin) {
plugin.category = 'enterprise'; plugin.category = 'enterprise';
plugin.unlicensed = edition !== 'Open Source' && !hasValidLicense; plugin.unlicensed = !featureEnabled('enterprise.plugins');
plugin.info.links = enterprisePlugin?.info?.links || plugin.info.links; plugin.info.links = enterprisePlugin?.info?.links || plugin.info.links;
} }

@ -1,4 +1,5 @@
import { DataSourceSettings, PluginType, PluginInclude, NavModel, NavModelItem } from '@grafana/data'; import { DataSourceSettings, PluginType, PluginInclude, NavModel, NavModelItem } from '@grafana/data';
import { featureEnabled } from '@grafana/runtime';
import config from 'app/core/config'; import config from 'app/core/config';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
import { AccessControlAction } from 'app/types'; 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)) { if (contextSrv.hasPermission(AccessControlAction.DataSourcesPermissionsRead)) {
navModel.children!.push({ navModel.children!.push({
active: false, active: false,
@ -57,7 +58,9 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat
url: `datasources/edit/${dataSource.id}/permissions`, url: `datasources/edit/${dataSource.id}/permissions`,
}); });
} }
}
if (featureEnabled('analytics')) {
navModel.children!.push({ navModel.children!.push({
active: false, active: false,
icon: 'info-circle', icon: 'info-circle',
@ -65,7 +68,9 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat
text: 'Insights', text: 'Insights',
url: `datasources/edit/${dataSource.id}/insights`, url: `datasources/edit/${dataSource.id}/insights`,
}); });
}
if (featureEnabled('caching')) {
navModel.children!.push({ navModel.children!.push({
active: false, active: false,
icon: 'database', icon: 'database',

@ -2,7 +2,7 @@ import React from 'react';
import { Badge, Button, HorizontalGroup, PluginSignatureBadge, useStyles2 } from '@grafana/ui'; import { Badge, Button, HorizontalGroup, PluginSignatureBadge, useStyles2 } from '@grafana/ui';
import { CatalogPlugin } from '../../types'; import { CatalogPlugin } from '../../types';
import { getBadgeColor } from './sharedStyles'; import { getBadgeColor } from './sharedStyles';
import { config } from '@grafana/runtime'; import { featureEnabled } from '@grafana/runtime';
type Props = { plugin: CatalogPlugin }; type Props = { plugin: CatalogPlugin };
@ -17,7 +17,7 @@ export function PluginEnterpriseBadge({ plugin }: Props): React.ReactElement {
); );
}; };
if (config.licenseInfo?.hasValidLicense) { if (featureEnabled('enterprise.plugins')) {
return <Badge text="Enterprise" color="blue" />; return <Badge text="Enterprise" color="blue" />;
} }

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { css } from '@emotion/css'; 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 { HorizontalGroup, Icon, LinkButton, useStyles2 } from '@grafana/ui';
import { GrafanaTheme2, PluginType } from '@grafana/data'; import { GrafanaTheme2, PluginType } from '@grafana/data';
@ -39,7 +39,7 @@ export const InstallControls = ({ plugin, latestCompatibleVersion }: Props) => {
return <div className={styles.message}>Renderer plugins cannot be managed by the Plugin Catalog.</div>; return <div className={styles.message}>Renderer plugins cannot be managed by the Plugin Catalog.</div>;
} }
if (plugin.isEnterprise && !config.licenseInfo?.hasValidLicense) { if (plugin.isEnterprise && !featureEnabled('enterprise.plugins')) {
return ( return (
<HorizontalGroup height="auto" align="center"> <HorizontalGroup height="auto" align="center">
<span className={styles.message}>No valid Grafana Enterprise license detected.</span> <span className={styles.message}>No valid Grafana Enterprise license detected.</span>

@ -49,14 +49,14 @@ describe('PluginListItemBadges', () => {
}); });
it('renders an enterprise badge (when a license is valid)', () => { it('renders an enterprise badge (when a license is valid)', () => {
config.licenseInfo.hasValidLicense = true; config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true };
render(<PluginListItemBadges plugin={{ ...plugin, isEnterprise: true }} />); render(<PluginListItemBadges plugin={{ ...plugin, isEnterprise: true }} />);
expect(screen.getByText(/enterprise/i)).toBeVisible(); expect(screen.getByText(/enterprise/i)).toBeVisible();
expect(screen.queryByRole('button', { name: /learn more/i })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: /learn more/i })).not.toBeInTheDocument();
}); });
it('renders an enterprise badge with icon and link (when a license is invalid)', () => { it('renders an enterprise badge with icon and link (when a license is invalid)', () => {
config.licenseInfo.hasValidLicense = false; config.licenseInfo.enabledFeatures = {};
render(<PluginListItemBadges plugin={{ ...plugin, isEnterprise: true }} />); render(<PluginListItemBadges plugin={{ ...plugin, isEnterprise: true }} />);
expect(screen.getByText(/enterprise/i)).toBeVisible(); expect(screen.getByText(/enterprise/i)).toBeVisible();
expect(screen.getByLabelText(/lock icon/i)).toBeInTheDocument(); expect(screen.getByLabelText(/lock icon/i)).toBeInTheDocument();

@ -90,7 +90,7 @@ describe('Plugin details page', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
config.pluginAdminExternalManageEnabled = false; config.pluginAdminExternalManageEnabled = false;
config.licenseInfo.hasValidLicense = false; config.licenseInfo.enabledFeatures = {};
}); });
afterAll(() => { afterAll(() => {
@ -325,7 +325,7 @@ describe('Plugin details page', () => {
}); });
it('should display an install button for enterprise plugins if license is valid', async () => { 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 }); 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 () => { 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 }); 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 () => { 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 }); const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: false, isEnterprise: true });
await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument()); await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument());

@ -7,10 +7,12 @@ import { User } from 'app/core/services/context_srv';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
jest.mock('app/core/config', () => ({ jest.mock('@grafana/runtime/src/config', () => ({
...((jest.requireActual('app/core/config') as unknown) as object), ...((jest.requireActual('@grafana/runtime/src/config') as unknown) as object),
config: {
licenseInfo: { licenseInfo: {
hasLicense: true, enabledFeatures: { teamsync: true },
},
}, },
})); }));

@ -13,6 +13,7 @@ import { getTeamLoadingNav } from './state/navModel';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { featureEnabled } from '@grafana/runtime';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
interface TeamPageRouteParams { interface TeamPageRouteParams {
@ -67,7 +68,7 @@ export class TeamPages extends PureComponent<Props, State> {
this.state = { this.state = {
isLoading: false, isLoading: false,
isSyncEnabled: config.licenseInfo.hasLicense, isSyncEnabled: featureEnabled('teamsync'),
}; };
} }

@ -1,5 +1,5 @@
import { Team, TeamPermissionLevel } from 'app/types'; import { Team, TeamPermissionLevel } from 'app/types';
import config from 'app/core/config'; import { featureEnabled } from '@grafana/runtime';
import { NavModelItem, NavModel } from '@grafana/data'; import { NavModelItem, NavModel } from '@grafana/data';
export function buildNavModel(team: Team): NavModelItem { 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({ navModel.children.push({
active: false, active: false,
icon: 'sync', icon: 'sync',

Loading…
Cancel
Save