diff --git a/x-pack/plugins/ml/common/license/index.ts b/x-pack/plugins/ml/common/license/index.ts index e87986a26a3bd..e09040a9a26f4 100644 --- a/x-pack/plugins/ml/common/license/index.ts +++ b/x-pack/plugins/ml/common/license/index.ts @@ -11,4 +11,5 @@ export { MINIMUM_LICENSE, isFullLicense, isMinimumLicense, + isMlEnabled, } from './ml_license'; diff --git a/x-pack/plugins/ml/common/license/ml_license.ts b/x-pack/plugins/ml/common/license/ml_license.ts index e4367c9b921f4..44ed892ff0d0a 100644 --- a/x-pack/plugins/ml/common/license/ml_license.ts +++ b/x-pack/plugins/ml/common/license/ml_license.ts @@ -82,3 +82,7 @@ export function isFullLicense(license: ILicense) { export function isMinimumLicense(license: ILicense) { return license.check(PLUGIN_ID, MINIMUM_LICENSE).state === 'valid'; } + +export function isMlEnabled(license: ILicense) { + return license.getFeature(PLUGIN_ID).isEnabled; +} diff --git a/x-pack/plugins/ml/public/application/management/index.ts b/x-pack/plugins/ml/public/application/management/index.ts index 897731304ee7a..a1b8484f200ec 100644 --- a/x-pack/plugins/ml/public/application/management/index.ts +++ b/x-pack/plugins/ml/public/application/management/index.ts @@ -11,37 +11,28 @@ */ import { i18n } from '@kbn/i18n'; -import { take } from 'rxjs/operators'; import { CoreSetup } from 'kibana/public'; -import { MlStartDependencies, MlSetupDependencies } from '../../plugin'; +import { ManagementSetup } from 'src/plugins/management/public'; +import { MlStartDependencies } from '../../plugin'; import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; -import { PLUGIN_ID } from '../../../common/constants/app'; -import { MINIMUM_FULL_LICENSE } from '../../../common/license'; -export function initManagementSection( - pluginsSetup: MlSetupDependencies, +export function registerManagementSection( + management: ManagementSetup | undefined, core: CoreSetup ) { - const licensing = pluginsSetup.licensing.license$.pipe(take(1)); - licensing.subscribe((license) => { - const management = pluginsSetup.management; - if ( - management !== undefined && - license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === 'valid' - ) { - management.sections.section.insightsAndAlerting.registerApp({ - id: 'jobsListLink', - title: i18n.translate('xpack.ml.management.jobsListTitle', { - defaultMessage: 'Machine Learning Jobs', - }), - order: 2, - async mount(params: ManagementAppMountParams) { - const { mountApp } = await import('./jobs_list'); - return mountApp(core, params); - }, - }); - } - }); + if (management !== undefined) { + management.sections.section.insightsAndAlerting.registerApp({ + id: 'jobsListLink', + title: i18n.translate('xpack.ml.management.jobsListTitle', { + defaultMessage: 'Machine Learning Jobs', + }), + order: 2, + async mount(params: ManagementAppMountParams) { + const { mountApp } = await import('./jobs_list'); + return mountApp(core, params); + }, + }); + } } diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 449d8baa2a184..a8e1e804c2fe3 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -12,6 +12,8 @@ import { AppMountParameters, PluginInitializerContext, } from 'kibana/public'; +import { BehaviorSubject } from 'rxjs'; +import { take } from 'rxjs/operators'; import { ManagementSetup } from 'src/plugins/management/public'; import { SharePluginSetup, SharePluginStart, UrlGeneratorState } from 'src/plugins/share/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; @@ -19,9 +21,10 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { EmbeddableSetup } from 'src/plugins/embeddable/public'; +import { AppStatus, AppUpdater } from '../../../../src/core/public'; import { SecurityPluginSetup } from '../../security/public'; import { LicensingPluginSetup } from '../../licensing/public'; -import { initManagementSection } from './application/management'; +import { registerManagementSection } from './application/management'; import { LicenseManagementUIPluginSetup } from '../../license_management/public'; import { setDependencyCache } from './application/util/dependency_cache'; import { PLUGIN_ID, PLUGIN_ICON } from '../common/constants/app'; @@ -31,7 +34,8 @@ import { registerEmbeddables } from './embeddables'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { registerMlUiActions } from './ui_actions'; import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public'; -import { MlUrlGenerator, MlUrlGeneratorState, ML_APP_URL_GENERATOR } from './url_generator'; +import { registerUrlGenerator, MlUrlGeneratorState, ML_APP_URL_GENERATOR } from './url_generator'; +import { isMlEnabled, isFullLicense } from '../common/license'; export interface MlStartDependencies { data: DataPublicPluginStart; @@ -61,18 +65,11 @@ declare module '../../../../src/plugins/share/public' { export type MlCoreSetup = CoreSetup; export class MlPlugin implements Plugin { + private appUpdater = new BehaviorSubject(() => ({})); + constructor(private initializerContext: PluginInitializerContext) {} setup(core: MlCoreSetup, pluginsSetup: MlSetupDependencies) { - const baseUrl = core.http.basePath.prepend('/app/ml'); - - pluginsSetup.share.urlGenerators.registerUrlGenerator( - new MlUrlGenerator({ - appBasePath: baseUrl, - useHash: core.uiSettings.get('state:storeInSessionStorage'), - }) - ); - core.application.register({ id: PLUGIN_ID, title: i18n.translate('xpack.ml.plugin.title', { @@ -82,6 +79,7 @@ export class MlPlugin implements Plugin { euiIconType: PLUGIN_ICON, appRoute: '/app/ml', category: DEFAULT_APP_CATEGORIES.kibana, + updater$: this.appUpdater, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); const kibanaVersion = this.initializerContext.env.packageInfo.version; @@ -112,11 +110,26 @@ export class MlPlugin implements Plugin { }, }); - registerFeature(pluginsSetup.home); + const licensing = pluginsSetup.licensing.license$.pipe(take(1)); + licensing.subscribe((license) => { + if (isMlEnabled(license)) { + // add ML to home page + registerFeature(pluginsSetup.home); - initManagementSection(pluginsSetup, core); - registerEmbeddables(pluginsSetup.embeddable, core); - registerMlUiActions(pluginsSetup.uiActions, core); + // register various ML plugin features which require a full license + if (isFullLicense(license)) { + registerManagementSection(pluginsSetup.management, core); + registerEmbeddables(pluginsSetup.embeddable, core); + registerMlUiActions(pluginsSetup.uiActions, core); + registerUrlGenerator(pluginsSetup.share, core); + } + } else { + // if ml is disabled in elasticsearch, disable ML in kibana + this.appUpdater.next(() => ({ + status: AppStatus.inaccessible, + })); + } + }); return {}; } diff --git a/x-pack/plugins/ml/public/url_generator.ts b/x-pack/plugins/ml/public/url_generator.ts index 65d5077e081a3..c2b57f6349d81 100644 --- a/x-pack/plugins/ml/public/url_generator.ts +++ b/x-pack/plugins/ml/public/url_generator.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public'; +import { CoreSetup } from 'kibana/public'; +import { SharePluginSetup, UrlGeneratorsDefinition } from '../../../../src/plugins/share/public'; import { TimeRange } from '../../../../src/plugins/data/public'; import { setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public'; import { JobId } from '../../reporting/common/types'; import { ExplorerAppState } from './application/explorer/explorer_dashboard_service'; +import { MlStartDependencies } from './plugin'; export const ML_APP_URL_GENERATOR = 'ML_APP_URL_GENERATOR'; @@ -88,3 +90,19 @@ export class MlUrlGenerator implements UrlGeneratorsDefinition +) { + const baseUrl = core.http.basePath.prepend('/app/ml'); + share.urlGenerators.registerUrlGenerator( + new MlUrlGenerator({ + appBasePath: baseUrl, + useHash: core.uiSettings.get('state:storeInSessionStorage'), + }) + ); +} diff --git a/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts b/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts index 5b8cbc4bdbbe8..f2fff4cc64aab 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts @@ -9,7 +9,7 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { CapabilitiesSwitcher, CoreSetup, Logger } from 'src/core/server'; import { ILicense } from '../../../../licensing/common/types'; -import { isFullLicense, isMinimumLicense } from '../../../common/license'; +import { isFullLicense, isMinimumLicense, isMlEnabled } from '../../../common/license'; import { MlCapabilities, basicLicenseMlCapabilities } from '../../../common/types/capabilities'; export const setupCapabilitiesSwitcher = ( @@ -30,9 +30,10 @@ function getSwitcher(license$: Observable, logger: Logger): Capabiliti try { const license = await license$.pipe(take(1)).toPromise(); + const mlEnabled = isMlEnabled(license); // full license, leave capabilities as they were - if (isFullLicense(license)) { + if (mlEnabled && isFullLicense(license)) { return capabilities; } @@ -45,7 +46,7 @@ function getSwitcher(license$: Observable, logger: Logger): Capabiliti }); // for a basic license, reapply the original capabilities for the basic license features - if (isMinimumLicense(license)) { + if (mlEnabled && isMinimumLicense(license)) { basicLicenseMlCapabilities.forEach((c) => (mlCaps[c] = originalCapabilities[c])); }