diff --git a/x-pack/legacy/plugins/ml/public/application/management/breadcrumbs.ts b/x-pack/legacy/plugins/ml/public/application/management/breadcrumbs.ts index d3bc498c50f821..b68f5d0be66459 100644 --- a/x-pack/legacy/plugins/ml/public/application/management/breadcrumbs.ts +++ b/x-pack/legacy/plugins/ml/public/application/management/breadcrumbs.ts @@ -5,12 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs'; import { JOBS_LIST_PATH } from './management_urls'; export function getJobsListBreadcrumbs() { return [ - MANAGEMENT_BREADCRUMB, { text: i18n.translate('xpack.ml.jobsList.breadcrumb', { defaultMessage: 'Jobs', diff --git a/x-pack/legacy/plugins/ml/public/application/management/index.ts b/x-pack/legacy/plugins/ml/public/application/management/index.ts index 99a2e8353a8742..d3dd1e4227531f 100644 --- a/x-pack/legacy/plugins/ml/public/application/management/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/management/index.ts @@ -10,22 +10,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { npSetup } from 'ui/new_platform'; -import { management } from 'ui/management'; +import { npSetup, npStart } from 'ui/new_platform'; import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { metadata } from 'ui/metadata'; import { take } from 'rxjs/operators'; -import { JOBS_LIST_PATH } from './management_urls'; -import { setDependencyCache } from '../util/dependency_cache'; -import './jobs_list'; + +import { ManagementSetup } from '../../../../../../../src/plugins/management/public'; + import { LicensingPluginSetup, LICENSE_CHECK_STATE, } from '../../../../../../plugins/licensing/public'; + import { PLUGIN_ID } from '../../../common/constants/app'; import { MINIMUM_FULL_LICENSE } from '../../../common/license'; +import { setDependencyCache } from '../util/dependency_cache'; + +import { getJobsListBreadcrumbs } from './breadcrumbs'; +import { renderApp } from './jobs_list'; + type PluginsSetupExtended = typeof npSetup.plugins & { // adds licensing which isn't in the PluginsSetup interface, but does exist licensing: LicensingPluginSetup; @@ -36,11 +41,10 @@ const plugins = npSetup.plugins as PluginsSetupExtended; const licensing = plugins.licensing.license$.pipe(take(1)); licensing.subscribe(license => { if (license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === LICENSE_CHECK_STATE.Valid) { - initManagementSection(); + initManagementSection(plugins.management); } }); - -function initManagementSection() { +function initManagementSection(management: ManagementSetup) { const legacyBasePath = { prepend: chrome.addBasePath, get: chrome.getBasePath, @@ -54,22 +58,27 @@ function initManagementSection() { setDependencyCache({ docLinks: legacyDocLinks as any, basePath: legacyBasePath as any, + http: npStart.core.http, }); - management.register('ml', { - display: i18n.translate('xpack.ml.management.mlTitle', { + const mlSection = management.sections.register({ + id: 'ml', + title: i18n.translate('xpack.ml.management.mlTitle', { defaultMessage: 'Machine Learning', }), order: 100, icon: 'machineLearningApp', }); - management.getSection('ml').register('jobsList', { - name: 'jobsListLink', - order: 10, - display: i18n.translate('xpack.ml.management.jobsListTitle', { + mlSection.registerApp({ + id: 'jobsListLink', + title: i18n.translate('xpack.ml.management.jobsListTitle', { defaultMessage: 'Jobs list', }), - url: `#${JOBS_LIST_PATH}`, + order: 10, + async mount({ element, setBreadcrumbs }) { + setBreadcrumbs(getJobsListBreadcrumbs()); + return renderApp(element, {}); + }, }); } diff --git a/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index a987ed7feeee94..f3080dcece9892 100644 --- a/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useState } from 'react'; +import React, { useEffect, useState, Fragment, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; import { @@ -18,15 +18,14 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { getDocLinks } from '../../../../util/dependency_cache'; +import { checkGetManagementMlJobs } from '../../../../privilege/check_privilege'; + +import { getDocLinks } from '../../../../util/dependency_cache'; // @ts-ignore undeclared module import { JobsListView } from '../../../../jobs/jobs_list/components/jobs_list_view/index'; import { DataFrameAnalyticsList } from '../../../../data_frame_analytics/pages/analytics_management/components/analytics_list'; -interface Props { - isMlEnabledInSpace: boolean; -} interface Tab { id: string; name: string; @@ -65,11 +64,33 @@ function getTabs(isMlEnabledInSpace: boolean): Tab[] { ]; } -export const JobsListPage: FC = ({ isMlEnabledInSpace }) => { - const docLinks = getDocLinks(); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; +export const JobsListPage: FC = () => { + const [initialized, setInitialized] = useState(false); + const [isMlEnabledInSpace, setIsMlEnabledInSpace] = useState(false); const tabs = getTabs(isMlEnabledInSpace); const [currentTabId, setCurrentTabId] = useState(tabs[0].id); + + const check = async () => { + try { + const checkPrivilege = await checkGetManagementMlJobs(); + setInitialized(true); + setIsMlEnabledInSpace(checkPrivilege.mlFeatureEnabledInSpace); + } catch (e) { + // Silent fail, `checkGetManagementMlJobs()` should redirect when + // there are insufficient permissions. + } + }; + + useEffect(() => { + check(); + }, []); + + if (initialized === false) { + return null; + } + + const docLinks = getDocLinks(); + const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; const anomalyDetectionJobsUrl = `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-jobs.html`; const anomalyJobsUrl = `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics.html`; @@ -98,7 +119,7 @@ export const JobsListPage: FC = ({ isMlEnabledInSpace }) => { return ( - + diff --git a/x-pack/legacy/plugins/ml/public/application/management/jobs_list/index.ts b/x-pack/legacy/plugins/ml/public/application/management/jobs_list/index.ts index b88138d139f607..1352fc1ef40b61 100644 --- a/x-pack/legacy/plugins/ml/public/application/management/jobs_list/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/management/jobs_list/index.ts @@ -4,52 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import ReactDOM, { render, unmountComponentAtNode } from 'react-dom'; +import ReactDOM, { unmountComponentAtNode } from 'react-dom'; import React from 'react'; -import routes from 'ui/routes'; -import { canGetManagementMlJobs } from '../../privilege/check_privilege'; -import { JOBS_LIST_PATH, ACCESS_DENIED_PATH } from '../management_urls'; -import { JobsListPage, AccessDeniedPage } from './components'; -import { getJobsListBreadcrumbs } from '../breadcrumbs'; +import { JobsListPage } from './components'; -const template = ` -
-`; +export const renderApp = (element: HTMLElement, appDependencies: any) => { + ReactDOM.render(React.createElement(JobsListPage), element); -routes.when(JOBS_LIST_PATH, { - template, - k7Breadcrumbs: getJobsListBreadcrumbs, - resolve: { - checkPrivilege: canGetManagementMlJobs, - }, - controller($scope, checkPrivilege) { - const { mlFeatureEnabledInSpace } = checkPrivilege; - - $scope.$on('$destroy', () => { - const elem = document.getElementById('kibanaManagementMLSection'); - if (elem) unmountComponentAtNode(elem); - }); - $scope.$$postDigest(() => { - const element = document.getElementById('kibanaManagementMLSection'); - ReactDOM.render( - React.createElement(JobsListPage, { isMlEnabledInSpace: mlFeatureEnabledInSpace }), - element - ); - }); - }, -}); - -routes.when(ACCESS_DENIED_PATH, { - template, - k7Breadcrumbs: getJobsListBreadcrumbs, - controller($scope) { - $scope.$on('$destroy', () => { - const elem = document.getElementById('kibanaManagementMLSection'); - if (elem) unmountComponentAtNode(elem); - }); - $scope.$$postDigest(() => { - const element = document.getElementById('kibanaManagementMLSection'); - render(AccessDeniedPage(), element); - }); - }, -}); + return () => { + unmountComponentAtNode(element); + }; +}; diff --git a/x-pack/legacy/plugins/ml/public/application/privilege/check_privilege.ts b/x-pack/legacy/plugins/ml/public/application/privilege/check_privilege.ts index ec9695a2ce6683..4de8c6eb703ffd 100644 --- a/x-pack/legacy/plugins/ml/public/application/privilege/check_privilege.ts +++ b/x-pack/legacy/plugins/ml/public/application/privilege/check_privilege.ts @@ -14,8 +14,8 @@ import { ACCESS_DENIED_PATH } from '../management/management_urls'; let privileges: Privileges = getDefaultPrivileges(); // manage_ml requires all monitor and admin cluster privileges: https://github.com/elastic/elasticsearch/blob/664a29c8905d8ce9ba8c18aa1ed5c5de93a0eabc/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java#L53 -export function canGetManagementMlJobs() { - return new Promise((resolve, reject) => { +export function checkGetManagementMlJobs() { + return new Promise<{ mlFeatureEnabledInSpace: boolean }>((resolve, reject) => { getManageMlPrivileges().then( ({ capabilities, isPlatinumOrTrialLicense, mlFeatureEnabledInSpace }) => { privileges = capabilities; diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.js b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.js index 688abd1383ecb4..5b2f213ffba9f6 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.js +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.js @@ -220,7 +220,8 @@ export const ml = { checkManageMLPrivileges() { return http({ - url: `${basePath()}/ml_capabilities?ignoreSpaces=true`, + // TODO Fix http service parameters + url: `${basePath()}/ml_capabilities`, // '?ignoreSpaces=true' method: 'GET', }); }, diff --git a/x-pack/legacy/plugins/ml/public/plugin.ts b/x-pack/legacy/plugins/ml/public/plugin.ts index 7b3a5f6fadfaca..1ef700f55b4dbc 100644 --- a/x-pack/legacy/plugins/ml/public/plugin.ts +++ b/x-pack/legacy/plugins/ml/public/plugin.ts @@ -14,8 +14,8 @@ export class MlPlugin implements Plugin { title: 'Machine learning', async mount(context, params) { const [coreStart, depsStart] = await core.getStartServices(); - const { renderApp } = await import('./application/app'); - return renderApp(coreStart, depsStart, { + const { renderApp: renderMlApp } = await import('./application/app'); + return renderMlApp(coreStart, depsStart, { element: params.element, appBasePath: params.appBasePath, onAppLeave: params.onAppLeave,