Skip to content

Commit

Permalink
[ML] Register NP ML plugin for Kibana management section. (#59762) (#…
Browse files Browse the repository at this point in the history
…59985)

Migrates registering the ML management section using React only, deprecates the angularJS approach.
  • Loading branch information
walterra authored Mar 12, 2020
1 parent 570235e commit c82f35d
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
39 changes: 24 additions & 15 deletions x-pack/legacy/plugins/ml/public/application/management/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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, {});
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -65,11 +64,33 @@ function getTabs(isMlEnabledInSpace: boolean): Tab[] {
];
}

export const JobsListPage: FC<Props> = ({ 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`;

Expand Down Expand Up @@ -98,7 +119,7 @@ export const JobsListPage: FC<Props> = ({ isMlEnabledInSpace }) => {

return (
<I18nContext>
<EuiPageContent>
<EuiPageContent id="kibanaManagementMLSection">
<EuiTitle size="l">
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `<kbn-management-app section="ml/jobs-list">
<div id="kibanaManagementMLSection" />
</kbn-management-app>`;
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);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
},
Expand Down
4 changes: 2 additions & 2 deletions x-pack/legacy/plugins/ml/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export class MlPlugin implements Plugin<Setup, Start> {
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,
Expand Down

0 comments on commit c82f35d

Please sign in to comment.