From 33716211b677868ef7d2732db812fc5d527192f8 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 10 Aug 2020 12:37:19 +0200 Subject: [PATCH] [ML] Fix analytics list on management page. (#74254) (#74527) The analytics job page in the Kibana management section didn't have the context provided by React Router and Kibana's own history object so the page crashed on load. The context is necessary to construct the correct URLs to navigate to the ML plugin itself. This PR fixes it by wrapping the management page in . Also adds functional tests to cover navigating to the jobs list pages in stack management. --- .../jobs_list_page/jobs_list_page.tsx | 96 +++++++++++-------- .../application/management/jobs_list/index.ts | 10 +- x-pack/test/functional/apps/ml/pages.ts | 12 +++ .../test/functional/services/ml/navigation.ts | 27 ++++++ 4 files changed, 101 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index 33bb78c51e013..0af6030df28b1 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -5,6 +5,7 @@ */ import React, { useEffect, useState, Fragment, FC } from 'react'; +import { Router } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { CoreStart } from 'kibana/public'; @@ -20,6 +21,8 @@ import { EuiTitle, } from '@elastic/eui'; +import { ManagementAppMountParams } from '../../../../../../../../../src/plugins/management/public/'; + import { checkGetManagementMlJobsResolver } from '../../../../capabilities/check_capabilities'; import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public'; @@ -30,6 +33,7 @@ import { DataFrameAnalyticsList } from '../../../../data_frame_analytics/pages/a import { AccessDeniedPage } from '../access_denied_page'; interface Tab { + 'data-test-subj': string; id: string; name: string; content: any; @@ -38,6 +42,7 @@ interface Tab { function getTabs(isMlEnabledInSpace: boolean): Tab[] { return [ { + 'data-test-subj': 'mlStackManagementJobsListAnomalyDetectionTab', id: 'anomaly_detection_jobs', name: i18n.translate('xpack.ml.management.jobsList.anomalyDetectionTab', { defaultMessage: 'Anomaly detection', @@ -50,6 +55,7 @@ function getTabs(isMlEnabledInSpace: boolean): Tab[] { ), }, { + 'data-test-subj': 'mlStackManagementJobsListAnalyticsTab', id: 'analytics_jobs', name: i18n.translate('xpack.ml.management.jobsList.analyticsTab', { defaultMessage: 'Analytics', @@ -67,7 +73,10 @@ function getTabs(isMlEnabledInSpace: boolean): Tab[] { ]; } -export const JobsListPage: FC<{ coreStart: CoreStart }> = ({ coreStart }) => { +export const JobsListPage: FC<{ + coreStart: CoreStart; + history: ManagementAppMountParams['history']; +}> = ({ coreStart, history }) => { const [initialized, setInitialized] = useState(false); const [accessDenied, setAccessDenied] = useState(false); const [isMlEnabledInSpace, setIsMlEnabledInSpace] = useState(false); @@ -128,46 +137,51 @@ export const JobsListPage: FC<{ coreStart: CoreStart }> = ({ coreStart }) => { return ( - - - - -

- {i18n.translate('xpack.ml.management.jobsList.jobsListTitle', { - defaultMessage: 'Machine Learning Jobs', - })} -

-
- - - {currentTabId === 'anomaly_detection_jobs' - ? anomalyDetectionDocsLabel - : analyticsDocsLabel} - - -
-
- - - - {i18n.translate('xpack.ml.management.jobsList.jobsListTagline', { - defaultMessage: 'View machine learning analytics and anomaly detection jobs.', - })} - - - - {renderTabs()} -
+ + + + + +

+ {i18n.translate('xpack.ml.management.jobsList.jobsListTitle', { + defaultMessage: 'Machine Learning Jobs', + })} +

+
+ + + {currentTabId === 'anomaly_detection_jobs' + ? anomalyDetectionDocsLabel + : analyticsDocsLabel} + + +
+
+ + + + {i18n.translate('xpack.ml.management.jobsList.jobsListTagline', { + defaultMessage: 'View machine learning analytics and anomaly detection jobs.', + })} + + + + {renderTabs()} +
+
); diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts index 81190a412abc0..afea5a573b8b5 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts +++ b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts @@ -14,8 +14,12 @@ import { getJobsListBreadcrumbs } from '../breadcrumbs'; import { setDependencyCache, clearCache } from '../../util/dependency_cache'; import './_index.scss'; -const renderApp = (element: HTMLElement, coreStart: CoreStart) => { - ReactDOM.render(React.createElement(JobsListPage, { coreStart }), element); +const renderApp = ( + element: HTMLElement, + history: ManagementAppMountParams['history'], + coreStart: CoreStart +) => { + ReactDOM.render(React.createElement(JobsListPage, { coreStart, history }), element); return () => { unmountComponentAtNode(element); clearCache(); @@ -37,5 +41,5 @@ export async function mountApp( params.setBreadcrumbs(getJobsListBreadcrumbs()); - return renderApp(params.element, coreStart); + return renderApp(params.element, params.history, coreStart); } diff --git a/x-pack/test/functional/apps/ml/pages.ts b/x-pack/test/functional/apps/ml/pages.ts index e2c80c8dab558..3691e6b1afcdc 100644 --- a/x-pack/test/functional/apps/ml/pages.ts +++ b/x-pack/test/functional/apps/ml/pages.ts @@ -53,5 +53,17 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataVisualizer.assertDataVisualizerImportDataCardExists(); await ml.dataVisualizer.assertDataVisualizerIndexDataCardExists(); }); + + it('should load the stack management with the ML menu item being present', async () => { + await ml.navigation.navigateToStackManagement(); + }); + + it('should load the jobs list page in stack management', async () => { + await ml.navigation.navigateToStackManagementJobsListPage(); + }); + + it('should load the analytics jobs list page in stack management', async () => { + await ml.navigation.navigateToStackManagementJobsListPageAnalyticsTab(); + }); }); } diff --git a/x-pack/test/functional/services/ml/navigation.ts b/x-pack/test/functional/services/ml/navigation.ts index 9b67a369f055f..f52197d4b2256 100644 --- a/x-pack/test/functional/services/ml/navigation.ts +++ b/x-pack/test/functional/services/ml/navigation.ts @@ -23,6 +23,13 @@ export function MachineLearningNavigationProvider({ }); }, + async navigateToStackManagement() { + await retry.tryForTime(60 * 1000, async () => { + await PageObjects.common.navigateToApp('management'); + await testSubjects.existOrFail('jobsListLink', { timeout: 2000 }); + }); + }, + async assertTabsExist(tabTypeSubject: string, areaSubjects: string[]) { await retry.tryForTime(10000, async () => { const allTabs = await testSubjects.findAll(`~${tabTypeSubject}`, 3); @@ -76,5 +83,25 @@ export function MachineLearningNavigationProvider({ async navigateToSettings() { await this.navigateToArea('~mlMainTab & ~settings', 'mlPageSettings'); }, + + async navigateToStackManagementJobsListPage() { + // clicks the jobsListLink and loads the jobs list page + await testSubjects.click('jobsListLink'); + await retry.tryForTime(60 * 1000, async () => { + // verify that the overall page is present + await testSubjects.existOrFail('mlPageStackManagementJobsList'); + // verify that the default tab with the anomaly detection jobs list got loaded + await testSubjects.existOrFail('ml-jobs-list'); + }); + }, + + async navigateToStackManagementJobsListPageAnalyticsTab() { + // clicks the `Analytics` tab and loads the analytics list page + await testSubjects.click('mlStackManagementJobsListAnalyticsTab'); + await retry.tryForTime(60 * 1000, async () => { + // verify that the empty prompt for analytics jobs list got loaded + await testSubjects.existOrFail('mlNoDataFrameAnalyticsFound'); + }); + }, }; }