diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 95c81e20e6ccb3..d3ddc5999e7d3e 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -63,7 +63,11 @@ function stringMatch(str: string | undefined, substr: string) { ); } -export const DataFrameAnalyticsList: FC = () => { +interface Props { + isManagementTable?: boolean; +} +// isManagementTable - for use in Kibana managagement ML section +export const DataFrameAnalyticsList: FC = ({ isManagementTable }) => { const [isInitialized, setIsInitialized] = useState(false); const [isLoading, setIsLoading] = useState(false); const [blockRefresh, setBlockRefresh] = useState(false); @@ -225,7 +229,7 @@ export const DataFrameAnalyticsList: FC = () => { ); } - const columns = getColumns(expandedRowItemIds, setExpandedRowItemIds); + const columns = getColumns(expandedRowItemIds, setExpandedRowItemIds, isManagementTable); const sorting = { sort: { diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 3549732b0edc6a..4362a0f8d71064 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -59,7 +59,8 @@ export const getTaskStateBadge = ( export const getColumns = ( expandedRowItemIds: DataFrameAnalyticsId[], - setExpandedRowItemIds: React.Dispatch> + setExpandedRowItemIds: React.Dispatch>, + isManagementTable: boolean = false ) => { const actions = getActions(); @@ -75,8 +76,8 @@ export const getColumns = ( // spread to a new array otherwise the component wouldn't re-render setExpandedRowItemIds([...expandedRowItemIds]); } - - return [ + // update possible column types to something like (FieldDataColumn | ComputedColumn | ActionsColumn)[] when they have been added to EUI + const columns: any[] = [ { align: RIGHT_ALIGNMENT, width: '40px', @@ -205,12 +206,17 @@ export const getColumns = ( }, width: '100px', }, - { + ]; + + if (isManagementTable === false) { + columns.push({ name: i18n.translate('xpack.ml.dataframe.analyticsList.tableActionLabel', { defaultMessage: 'Actions', }), actions, width: '200px', - }, - ]; + }); + } + + return columns; }; diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list/jobs_list.js index d3145cb7978642..1a6243413d614b 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list/jobs_list.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list/jobs_list.js @@ -80,6 +80,11 @@ class JobsListUI extends Component { }; getJobIdLink(id) { + // Don't allow link to job if ML is not enabled in current space + if (this.props.isMlEnabledInSpace === false) { + return id; + } + return ( {id} @@ -253,6 +258,10 @@ class JobsListUI extends Component { {'all'} ) }); + // Remove actions if Ml not enabled in current space + if (this.props.isMlEnabledInSpace === false) { + columns.pop(); + } } else { // insert before last column columns.splice(columns.length - 1, 0, { @@ -344,6 +353,8 @@ class JobsListUI extends Component { JobsListUI.propTypes = { jobsSummaryList: PropTypes.array.isRequired, fullJobsList: PropTypes.object.isRequired, + isManagementTable: PropTypes.bool, + isMlEnabledInSpace: PropTypes.bool, itemIdToExpandedRowMap: PropTypes.object.isRequired, toggleRow: PropTypes.func.isRequired, selectJobChange: PropTypes.func.isRequired, @@ -355,6 +366,8 @@ JobsListUI.propTypes = { loading: PropTypes.bool, }; JobsListUI.defaultProps = { + isManagementTable: false, + isMlEnabledInSpace: true, loading: false, }; diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index 985275acf7d51c..fa55ef0fdda0c1 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -362,21 +362,22 @@ export class JobsListView extends Component { } renderManagementJobsListComponents() { - const { loading } = this.state; + const { loading, itemIdToExpandedRowMap, filteredJobsSummaryList, fullJobsList, selectedJobs } = this.state; return (
); diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/utils.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/utils.js index d0a44549c53338..e149261b7971d4 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/utils.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/utils.js @@ -8,7 +8,7 @@ import { each } from 'lodash'; import { toastNotifications } from 'ui/notify'; import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service'; import rison from 'rison-node'; -import chrome from 'ui/chrome'; // TODO: get from context once walter's PR is merged +import chrome from 'ui/chrome'; import { mlJobService } from 'plugins/ml/services/job_service'; import { ml } from 'plugins/ml/services/ml_api_service'; diff --git a/x-pack/legacy/plugins/ml/public/management/index.ts b/x-pack/legacy/plugins/ml/public/management/index.ts index 08e17a2a44f734..744b03c3d592bc 100644 --- a/x-pack/legacy/plugins/ml/public/management/index.ts +++ b/x-pack/legacy/plugins/ml/public/management/index.ts @@ -15,9 +15,13 @@ import { management } from 'ui/management'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; import { i18n } from '@kbn/i18n'; import { JOBS_LIST_PATH } from './management_urls'; +import { LICENSE_TYPE } from '../../common/constants/license'; import 'plugins/ml/management/jobs_list'; -if (xpackInfo.get('features.ml.showLinks', false) === true) { +if ( + xpackInfo.get('features.ml.showLinks', false) === true && + xpackInfo.get('features.ml.licenseType') === LICENSE_TYPE.FULL +) { management.register('ml', { display: i18n.translate('xpack.ml.management.mlTitle', { defaultMessage: 'Machine Learning', diff --git a/x-pack/legacy/plugins/ml/public/management/jobs_list/components/_index.scss b/x-pack/legacy/plugins/ml/public/management/jobs_list/components/_index.scss index 9fdce0d98e5c4e..883ecd96745b42 100644 --- a/x-pack/legacy/plugins/ml/public/management/jobs_list/components/_index.scss +++ b/x-pack/legacy/plugins/ml/public/management/jobs_list/components/_index.scss @@ -1,3 +1,4 @@ @import './jobs_list_page/stats_bar'; @import './jobs_list_page/buttons'; @import './jobs_list_page/expanded_row'; +@import './jobs_list_page/analytics_table'; diff --git a/x-pack/legacy/plugins/ml/public/management/jobs_list/components/jobs_list_page/_analytics_table.scss b/x-pack/legacy/plugins/ml/public/management/jobs_list/components/jobs_list_page/_analytics_table.scss new file mode 100644 index 00000000000000..ce53c33ca9ece6 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/management/jobs_list/components/jobs_list_page/_analytics_table.scss @@ -0,0 +1,28 @@ + +.mlAnalyticsTable { + // Using an override as a last resort because we cannot set custom classes on + // nested upstream components. The opening animation limits the height + // of the expanded row to 1000px which turned out to be not predictable. + // The animation could also result in flickering with expanded rows + // where the inner content would result in the DOM changing the height. + .euiTableRow-isExpandedRow .euiTableCellContent { + animation: none !important; + .euiTableCellContent__text { + width: 100%; + } + } + // Another override: Because an update to the table replaces the DOM, the same + // icon would still again fade in with an animation. If the table refreshes with + // e.g. 1s this would result in a blinking icon effect. + .euiIcon-isLoaded { + animation: none !important; + } +} + +.mlAnalyticsProgressBar { + margin-bottom: $euiSizeM; +} + +.mlTaskStateBadge { + max-width: 100px; +} diff --git a/x-pack/legacy/plugins/ml/public/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/legacy/plugins/ml/public/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index 852e33d92e5a83..6bdfa64833a394 100644 --- a/x-pack/legacy/plugins/ml/public/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/legacy/plugins/ml/public/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 } from 'react'; +import React, { Fragment, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; import { @@ -19,8 +19,13 @@ import { // @ts-ignore undeclared module import { JobsListView } from '../../../../jobs/jobs_list/components/jobs_list_view'; +import { DataFrameAnalyticsList } from '../../../../data_frame_analytics/pages/analytics_management/components/analytics_list'; -export const JobsListPage = () => { +interface Props { + isMlEnabledInSpace: boolean; +} + +export const JobsListPage: FC = ({ isMlEnabledInSpace }) => { const tabs = [ { id: 'anomaly_detection_jobs', @@ -30,23 +35,24 @@ export const JobsListPage = () => { content: ( - + + + ), + }, + { + id: 'analytics_jobs', + name: i18n.translate('xpack.ml.management.jobsList.analyticsTab', { + defaultMessage: 'Analytics', + }), + content: ( + + + ), }, - // { - // id: 'analytics_jobs', - // name: i18n.translate('xpack.ml.management.jobsList.analyticsTab', { - // defaultMessage: 'Analytics', - // }), - // content: renderAnalyticsJobs(), - // }, ]; - // function renderAnalyticsJobs() { - // return
Analytics job placeholder
; - // } - function renderTabs() { return ; } diff --git a/x-pack/legacy/plugins/ml/public/management/jobs_list/index.ts b/x-pack/legacy/plugins/ml/public/management/jobs_list/index.ts index 071d5ab6c6dfab..b88138d139f607 100644 --- a/x-pack/legacy/plugins/ml/public/management/jobs_list/index.ts +++ b/x-pack/legacy/plugins/ml/public/management/jobs_list/index.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore no declaration module -import { ReactDOM, render, unmountComponentAtNode } from 'react-dom'; +import ReactDOM, { render, 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'; @@ -22,14 +22,19 @@ routes.when(JOBS_LIST_PATH, { resolve: { checkPrivilege: canGetManagementMlJobs, }, - controller($scope) { + 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'); - render(JobsListPage(), element); + ReactDOM.render( + React.createElement(JobsListPage, { isMlEnabledInSpace: mlFeatureEnabledInSpace }), + element + ); }); }, }); diff --git a/x-pack/legacy/plugins/ml/public/privilege/check_privilege.ts b/x-pack/legacy/plugins/ml/public/privilege/check_privilege.ts index 8ea4b39ffe2f60..7f2985eda9da89 100644 --- a/x-pack/legacy/plugins/ml/public/privilege/check_privilege.ts +++ b/x-pack/legacy/plugins/ml/public/privilege/check_privilege.ts @@ -17,18 +17,20 @@ 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(kbnUrl: any) { return new Promise((resolve, reject) => { - getManageMlPrivileges().then(({ capabilities, isPlatinumOrTrialLicense }) => { - privileges = capabilities; - // Loop through all privilages to ensure they are all set to true. - const isManageML = Object.values(privileges).every(p => p === true); + getManageMlPrivileges().then( + ({ capabilities, isPlatinumOrTrialLicense, mlFeatureEnabledInSpace }) => { + privileges = capabilities; + // Loop through all privilages to ensure they are all set to true. + const isManageML = Object.values(privileges).every(p => p === true); - if (isManageML === true && isPlatinumOrTrialLicense === true) { - return resolve(); - } else { - kbnUrl.redirect(ACCESS_DENIED_PATH); - return reject(); + if (isManageML === true && isPlatinumOrTrialLicense === true) { + return resolve({ mlFeatureEnabledInSpace }); + } else { + kbnUrl.redirect(ACCESS_DENIED_PATH); + return reject(); + } } - }); + ); }); } diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts b/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts index ae73ba5be22bba..0ce60fffd836d5 100644 --- a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts +++ b/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts @@ -26,7 +26,8 @@ interface Response { export function privilegesProvider( callWithRequest: callWithRequestType, xpackMainPlugin: XPackMainPlugin, - isMlEnabledInSpace: () => Promise + isMlEnabledInSpace: () => Promise, + ignoreSpaces: boolean = false ) { const { isUpgradeInProgress } = upgradeCheckProvider(callWithRequest); async function getPrivileges(): Promise { @@ -47,7 +48,7 @@ export function privilegesProvider( ? setFullActionPrivileges : setBasicActionPrivileges; - if (mlFeatureEnabledInSpace === false) { + if (mlFeatureEnabledInSpace === false && ignoreSpaces === false) { // if ML isn't enabled in the current space, // return with the default privileges (all false) return { diff --git a/x-pack/legacy/plugins/ml/server/routes/system.js b/x-pack/legacy/plugins/ml/server/routes/system.js index 685117826c2dd6..787fb87404581e 100644 --- a/x-pack/legacy/plugins/ml/server/routes/system.js +++ b/x-pack/legacy/plugins/ml/server/routes/system.js @@ -99,12 +99,12 @@ export function systemRoutes({ const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); try { const ignoreSpaces = request.query && request.query.ignoreSpaces === 'true'; - // if spaces is disabled or ignoreSpace is true force isMlEnabledInSpace to be true - const { isMlEnabledInSpace } = (spacesPlugin !== undefined && ignoreSpaces === false) ? + // if spaces is disabled force isMlEnabledInSpace to be true + const { isMlEnabledInSpace } = spacesPlugin !== undefined ? spacesUtilsProvider(spacesPlugin, request, config) : { isMlEnabledInSpace: async () => true }; - const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPlugin, isMlEnabledInSpace); + const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPlugin, isMlEnabledInSpace, ignoreSpaces); return await getPrivileges(); } catch (error) { return wrapError(error);