diff --git a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx index 872337f71fe82..39672b14bf836 100644 --- a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx +++ b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx @@ -111,6 +111,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { }), pathId: ML_PAGES.SINGLE_METRIC_VIEWER, disabled: disableLinks, + testSubj: 'mlMainTab singleMetricViewer', }, { id: 'settings', @@ -185,7 +186,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { defaultMessage: 'File', }), disabled: false, - testSubj: 'mlMainTab dataVisualizer fileDatavisualizer', + testSubj: 'mlMainTab fileDataVisualizer', }, { id: 'data_view_datavisualizer', @@ -194,7 +195,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { defaultMessage: 'Data View', }), disabled: false, - testSubj: 'mlMainTab dataVisualizer dataViewDatavisualizer', + testSubj: 'mlMainTab indexDataVisualizer', }, ], }, diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx b/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx index e08e75143447c..ae04a7a3b2448 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx +++ b/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx @@ -11,6 +11,7 @@ export interface StatsBarStat { label: string; value: number; show?: boolean; + 'data-test-subj'?: string; } interface StatProps { stat: StatsBarStat; @@ -19,7 +20,8 @@ interface StatProps { export const Stat: FC = ({ stat }) => { return ( - {stat.label}: {stat.value} + {stat.label}:{' '} + {stat.value} ); }; diff --git a/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx b/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx index f9d734f7b84d5..457bca80ee2c0 100644 --- a/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx @@ -31,6 +31,7 @@ export const GettingStartedCallout: FC = () => { return ( <> { />

- + = ({ compactView = false }) => { label: i18n.translate('xpack.ml.trainedModels.nodesList.totalAmountLabel', { defaultMessage: 'Total machine learning nodes', }), + 'data-test-subj': 'mlTotalNodesCount', }, }; }, [items]); @@ -189,7 +190,7 @@ export const NodesList: FC = ({ compactView = false }) => { } return ( - <> +

{nodesStats && ( @@ -218,6 +219,6 @@ export const NodesList: FC = ({ compactView = false }) => { data-test-subj={isLoading ? 'mlNodesTable loading' : 'mlNodesTable loaded'} />
- + ); }; diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index 71c0d101943b9..c038aeba608bd 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -14,6 +14,7 @@ import { USER } from '../../../services/ml/security_common'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); + const browser = getService('browser'); const testUsers = [ { user: USER.ML_POWERUSER, discoverAvailable: true }, @@ -44,7 +45,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.navigation.assertKibanaNavMLEntryExists(); }); - it('should display tabs in the ML app correctly', async () => { + it('should display side nav in the ML app correctly', async () => { await ml.testExecution.logTestStep('should load the ML app'); await ml.navigation.navigateToMl(); @@ -52,33 +53,60 @@ export default function ({ getService }: FtrProviderContext) { await ml.navigation.assertOverviewTabEnabled(true); await ml.testExecution.logTestStep( - 'should display the enabled "Anomaly Detection" tab' + 'should display the enabled "Anomaly Detection" section correctly' ); await ml.navigation.assertAnomalyDetectionTabEnabled(true); + await ml.navigation.assertAnomalyExplorerNavItemEnabled(true); + await ml.navigation.assertSingleMetricViewerNavItemEnabled(true); + await ml.navigation.assertSettingsTabEnabled(true); await ml.testExecution.logTestStep( - 'should display the enabled "Data Frame Analytics" tab' + 'should display the enabled "Data Frame Analytics" section' ); await ml.navigation.assertDataFrameAnalyticsTabEnabled(true); - await ml.testExecution.logTestStep('should display the enabled "Data Visualizer" tab'); - await ml.navigation.assertDataVisualizerTabEnabled(true); + await ml.testExecution.logTestStep( + 'should display the enabled "Model Management" section' + ); + await ml.navigation.assertTrainedModelsNavItemEnabled(true); + await ml.navigation.assertNodesNavItemEnabled(true); - await ml.testExecution.logTestStep('should display the enabled "Settings" tab'); - await ml.navigation.assertSettingsTabEnabled(true); + await ml.testExecution.logTestStep( + 'should display the enabled "Data Visualizer" section' + ); + await ml.navigation.assertDataVisualizerTabEnabled(true); + await ml.navigation.assertFileDataVisualizerNavItemEnabled(true); + await ml.navigation.assertIndexDataVisualizerNavItemEnabled(true); }); it('should display elements on ML Overview page correctly', async () => { await ml.testExecution.logTestStep('should load the ML overview page'); await ml.navigation.navigateToOverview(); - await ml.testExecution.logTestStep('should display enabled AD create job button'); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + + await ml.testExecution.logTestStep('should display a welcome callout'); + await ml.overviewPage.assertGettingStartedCalloutVisible(true); + await ml.overviewPage.dismissGettingStartedCallout(); + + await ml.testExecution.logTestStep('should display ML Nodes panel'); + await ml.mlNodesPanel.assertNodeOverviewPanel(); + + await ml.testExecution.logTestStep('should display Anomaly Detection empty state'); + await ml.overviewPage.assertADEmptyStateExists(); await ml.overviewPage.assertADCreateJobButtonExists(); await ml.overviewPage.assertADCreateJobButtonEnabled(true); - await ml.testExecution.logTestStep('should display enabled DFA create job button'); + await ml.testExecution.logTestStep('should display DFA empty state'); + await ml.overviewPage.assertDFAEmptyStateExists(); await ml.overviewPage.assertDFACreateJobButtonExists(); await ml.overviewPage.assertDFACreateJobButtonEnabled(true); + + await ml.testExecution.logTestStep( + 'should persist the getting started callout state after refresh' + ); + await browser.refresh(); + await ml.overviewPage.assertGettingStartedCalloutVisible(false); }); }); } @@ -164,6 +192,21 @@ export default function ({ getService }: FtrProviderContext) { await ml.securityUI.logout(); }); + it('should display elements on ML Overview page correctly', async () => { + await ml.testExecution.logTestStep('should load the Overview page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToOverview(); + + await ml.testExecution.logTestStep('should display ML Nodes panel'); + await ml.mlNodesPanel.assertNodeOverviewPanel(); + + await ml.testExecution.logTestStep('should display Anomaly Detection panel'); + await ml.overviewPage.assertAdJobsOverviewPanelExist(); + + await ml.testExecution.logTestStep('should display DFA panel'); + await ml.overviewPage.assertDFAJobsOverviewPanelExist(); + }); + it('should display elements on Anomaly Detection page correctly', async () => { await ml.testExecution.logTestStep('should load the AD job management page'); await ml.navigation.navigateToMl(); diff --git a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts index 9abb30548b0eb..fd9cb2cb4c79e 100644 --- a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts @@ -52,20 +52,30 @@ export default function ({ getService }: FtrProviderContext) { await ml.navigation.assertOverviewTabEnabled(true); await ml.testExecution.logTestStep( - 'should display the enabled "Anomaly Detection" tab' + 'should display the enabled "Anomaly Detection" section correctly' ); await ml.navigation.assertAnomalyDetectionTabEnabled(true); + await ml.navigation.assertAnomalyExplorerNavItemEnabled(true); + await ml.navigation.assertSingleMetricViewerNavItemEnabled(true); + await ml.navigation.assertSettingsTabEnabled(true); await ml.testExecution.logTestStep( - 'should display the enabled "Data Frame Analytics" tab' + 'should display the enabled "Data Frame Analytics" section' ); await ml.navigation.assertDataFrameAnalyticsTabEnabled(true); - await ml.testExecution.logTestStep('should display the enabled "Data Visualizer" tab'); - await ml.navigation.assertDataVisualizerTabEnabled(true); + await ml.testExecution.logTestStep( + 'should display the enabled "Model Management" section' + ); + await ml.navigation.assertTrainedModelsNavItemEnabled(true); + await ml.navigation.assertNodesNavItemEnabled(false); - await ml.testExecution.logTestStep('should display the enabled "Settings" tab'); - await ml.navigation.assertSettingsTabEnabled(true); + await ml.testExecution.logTestStep( + 'should display the enabled "Data Visualizer" section' + ); + await ml.navigation.assertDataVisualizerTabEnabled(true); + await ml.navigation.assertFileDataVisualizerNavItemEnabled(true); + await ml.navigation.assertIndexDataVisualizerNavItemEnabled(true); }); it('should display elements on ML Overview page correctly', async () => { @@ -73,11 +83,22 @@ export default function ({ getService }: FtrProviderContext) { await ml.navigation.navigateToMl(); await ml.navigation.navigateToOverview(); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + + await ml.testExecution.logTestStep('should display a welcome callout'); + await ml.overviewPage.assertGettingStartedCalloutVisible(true); + await ml.overviewPage.dismissGettingStartedCallout(); + + await ml.testExecution.logTestStep('should not display ML Nodes panel'); + await ml.mlNodesPanel.assertNodesOverviewPanelExists(false); + await ml.testExecution.logTestStep('should display disabled AD create job button'); + await ml.overviewPage.assertADEmptyStateExists(); await ml.overviewPage.assertADCreateJobButtonExists(); await ml.overviewPage.assertADCreateJobButtonEnabled(false); await ml.testExecution.logTestStep('should display disabled DFA create job button'); + await ml.overviewPage.assertDFAEmptyStateExists(); await ml.overviewPage.assertDFACreateJobButtonExists(); await ml.overviewPage.assertDFACreateJobButtonEnabled(false); }); diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts index cb9ef179f0626..d6b75f53578a8 100644 --- a/x-pack/test/functional/services/ml/common_ui.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -334,5 +334,9 @@ export function MachineLearningCommonUIProvider({ await PageObjects.spaceSelector.goToSpecificSpace(spaceId); await PageObjects.spaceSelector.expectHomePage(spaceId); }, + + async waitForDatePickerIndicatorLoaded() { + await testSubjects.waitForEnabled('superDatePickerApplyTimeButton'); + }, }; } diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index 4b48e4c0269eb..f7fd5efefda33 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -54,6 +54,7 @@ import { MachineLearningDashboardEmbeddablesProvider } from './dashboard_embedda import { TrainedModelsProvider } from './trained_models'; import { TrainedModelsTableProvider } from './trained_models_table'; import { MachineLearningJobAnnotationsProvider } from './job_annotations_table'; +import { MlNodesPanelProvider } from './ml_nodes_list'; export function MachineLearningProvider(context: FtrProviderContext) { const commonAPI = MachineLearningCommonAPIProvider(context); @@ -124,6 +125,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const swimLane = SwimLaneProvider(context); const trainedModels = TrainedModelsProvider(context, api, commonUI); const trainedModelsTable = TrainedModelsTableProvider(context); + const mlNodesPanel = MlNodesPanelProvider(context); return { anomaliesTable, @@ -173,5 +175,6 @@ export function MachineLearningProvider(context: FtrProviderContext) { testResources, trainedModels, trainedModelsTable, + mlNodesPanel, }; } diff --git a/x-pack/test/functional/services/ml/ml_nodes_list.ts b/x-pack/test/functional/services/ml/ml_nodes_list.ts new file mode 100644 index 0000000000000..37cd4143e26cc --- /dev/null +++ b/x-pack/test/functional/services/ml/ml_nodes_list.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function MlNodesPanelProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async assertNodesOverviewPanelExists(expectPanelExits: boolean = true) { + if (expectPanelExits) { + await testSubjects.existOrFail('mlNodesOverviewPanel'); + } else { + await testSubjects.missingOrFail('mlNodesOverviewPanel'); + } + }, + + async assertNodesListLoaded() { + await testSubjects.existOrFail('mlNodesTable loaded', { timeout: 5000 }); + }, + + async assertMlNodesCount(minCount: number = 1) { + const actualCount = parseInt(await testSubjects.getVisibleText('mlTotalNodesCount'), 10); + expect(actualCount).to.not.be.lessThan( + minCount, + `Total ML nodes count should be at least '${minCount}' (got '${actualCount}')` + ); + }, + + async assertNodeOverviewPanel() { + await this.assertNodesOverviewPanelExists(); + await this.assertNodesListLoaded(); + await this.assertMlNodesCount(); + }, + }; +} diff --git a/x-pack/test/functional/services/ml/navigation.ts b/x-pack/test/functional/services/ml/navigation.ts index c11721453d10f..6bf753926c72a 100644 --- a/x-pack/test/functional/services/ml/navigation.ts +++ b/x-pack/test/functional/services/ml/navigation.ts @@ -106,14 +106,38 @@ export function MachineLearningNavigationProvider({ await this.assertTabEnabled('~mlMainTab & ~anomalyDetection', expectedValue); }, + async assertAnomalyExplorerNavItemEnabled(expectedValue: boolean) { + await this.assertTabEnabled('~mlMainTab & ~anomalyExplorer', expectedValue); + }, + + async assertSingleMetricViewerNavItemEnabled(expectedValue: boolean) { + await this.assertTabEnabled('~mlMainTab & ~singleMetricViewer', expectedValue); + }, + async assertDataFrameAnalyticsTabEnabled(expectedValue: boolean) { await this.assertTabEnabled('~mlMainTab & ~dataFrameAnalytics', expectedValue); }, + async assertTrainedModelsNavItemEnabled(expectedValue: boolean) { + await this.assertTabEnabled('~mlMainTab & ~trainedModels', expectedValue); + }, + + async assertNodesNavItemEnabled(expectedValue: boolean) { + await this.assertTabEnabled('~mlMainTab & ~nodesOverview', expectedValue); + }, + async assertDataVisualizerTabEnabled(expectedValue: boolean) { await this.assertTabEnabled('~mlMainTab & ~dataVisualizer', expectedValue); }, + async assertFileDataVisualizerNavItemEnabled(expectedValue: boolean) { + await this.assertTabEnabled('~mlMainTab & ~fileDataVisualizer', expectedValue); + }, + + async assertIndexDataVisualizerNavItemEnabled(expectedValue: boolean) { + await this.assertTabEnabled('~mlMainTab & ~indexDataVisualizer', expectedValue); + }, + async assertSettingsTabEnabled(expectedValue: boolean) { await this.assertTabEnabled('~mlMainTab & ~settings', expectedValue); }, diff --git a/x-pack/test/functional/services/ml/overview_page.ts b/x-pack/test/functional/services/ml/overview_page.ts index 8fc04dfa29b18..5f02edde0f310 100644 --- a/x-pack/test/functional/services/ml/overview_page.ts +++ b/x-pack/test/functional/services/ml/overview_page.ts @@ -13,6 +13,24 @@ export function MachineLearningOverviewPageProvider({ getService }: FtrProviderC const testSubjects = getService('testSubjects'); return { + async assertGettingStartedCalloutVisible(expectVisible: boolean = true) { + if (expectVisible) { + await testSubjects.existOrFail('mlGettingStartedCallout'); + } else { + await testSubjects.missingOrFail('mlGettingStartedCallout'); + } + }, + + async dismissGettingStartedCallout() { + await this.assertGettingStartedCalloutVisible(true); + await testSubjects.click('mlDismissGettingStartedCallout'); + await this.assertGettingStartedCalloutVisible(false); + }, + + async assertADEmptyStateExists() { + await testSubjects.existOrFail('mlAnomalyDetectionEmptyState'); + }, + async assertADCreateJobButtonExists() { await testSubjects.existOrFail('mlCreateNewJobButton'); }, @@ -27,6 +45,14 @@ export function MachineLearningOverviewPageProvider({ getService }: FtrProviderC ); }, + async assertAdJobsOverviewPanelExist() { + await testSubjects.existOrFail('mlOverviewTableAnomalyDetection'); + }, + + async assertDFAEmptyStateExists() { + await testSubjects.existOrFail('mlNoDataFrameAnalyticsFound'); + }, + async assertDFACreateJobButtonExists() { await testSubjects.existOrFail('mlAnalyticsCreateFirstButton'); }, @@ -41,6 +67,10 @@ export function MachineLearningOverviewPageProvider({ getService }: FtrProviderC ); }, + async assertDFAJobsOverviewPanelExist() { + await testSubjects.existOrFail('mlOverviewTableAnalytics'); + }, + async assertJobSyncRequiredWarningExists() { await testSubjects.existOrFail('mlJobSyncRequiredWarning', { timeout: 5000 }); },