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 });
},