From a6174d4a63e3ef197a6c3d8c68b53427131de619 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 18 Mar 2024 13:13:45 -0600 Subject: [PATCH] [ML] Single metric viewer in dashboards: adds functional test (#178768) ## Summary Related meta issue: https://github.com/elastic/kibana/issues/176651 This PR adds functional tests for SMV in dashboards. Flaky test runner: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5485 ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../single_metric_viewer_initializer.tsx | 2 +- .../constants.ts | 1 + .../anomaly_detection_integrations/index.ts | 1 + ...gle_metric_viewer_dashboard_embeddables.ts | 101 ++++++++++++++++++ .../services/ml/dashboard_embeddables.ts | 34 +++++- .../ml/dashboard_job_selection_table.ts | 28 +++++ 6 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 x-pack/test/functional/apps/ml/anomaly_detection_integrations/single_metric_viewer_dashboard_embeddables.ts diff --git a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_initializer.tsx b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_initializer.tsx index 94f0621d747f8..fa68adf9f9955 100644 --- a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_initializer.tsx +++ b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_initializer.tsx @@ -137,7 +137,7 @@ export const SingleMetricViewerInitializer: FC { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await ml.testResources.createDataViewIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + await PageObjects.common.setTime({ from, to }); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + await ml.testResources.deleteDataViewByTitle('ft_farequote'); + await PageObjects.common.unsetTime(); + }); + + for (const testData of testDataList) { + describe(testData.suiteSuffix, function () { + before(async () => { + await ml.api.createAndRunAnomalyDetectionLookbackJob( + testData.jobConfig, + testData.datafeedConfig + ); + await PageObjects.dashboard.navigateToApp(); + }); + + after(async () => { + await ml.testResources.deleteDashboardByTitle(testData.dashboardTitle); + }); + + it('can open job selection flyout', async () => { + await PageObjects.dashboard.clickNewDashboard(); + await ml.dashboardEmbeddables.assertDashboardIsEmpty(); + await ml.dashboardEmbeddables.openAnomalyJobSelectionFlyout( + ML_EMBEDDABLE_TYPES.ANOMALY_SINGLE_METRIC_VIEWER_EMBEDDABLE_TYPE + ); + }); + + it('can select jobs', async () => { + await ml.dashboardJobSelectionTable.setRowRadioButtonState( + testData.jobConfig.job_id, + true + ); + await ml.dashboardJobSelectionTable.applyJobSelection(); + }); + + it('can configure single metric viewer panel', async () => { + await ml.dashboardEmbeddables.assertSingleMetricViewerEmbeddableInitializerExists(); + await ml.singleMetricViewer.assertDetectorInputExist(); + await ml.singleMetricViewer.assertDetectorInputValue( + testData.expected.detectorInputValue + ); + await ml.singleMetricViewer.assertEntityInputExist(testData.expected.entityInputLabel); + await ml.singleMetricViewer.selectEntityValue( + testData.expected.entityInputLabel, + testData.expected.entityInputValue + ); + }); + + it('create new single metric viewer panel', async () => { + await ml.dashboardEmbeddables.clickSingleMetricViewerInitializerConfirmButtonEnabled(); + await PageObjects.timePicker.pauseAutoRefresh(); + await ml.dashboardEmbeddables.assertDashboardPanelExists(testData.panelTitle); + await ml.singleMetricViewer.assertChartExist(); + await ml.singleMetricViewer.assertAnomalyMarkerExist(); + await PageObjects.dashboard.saveDashboard(testData.dashboardTitle); + }); + }); + } + }); +} diff --git a/x-pack/test/functional/services/ml/dashboard_embeddables.ts b/x-pack/test/functional/services/ml/dashboard_embeddables.ts index d6328b6aa72b5..587a5762f4487 100644 --- a/x-pack/test/functional/services/ml/dashboard_embeddables.ts +++ b/x-pack/test/functional/services/ml/dashboard_embeddables.ts @@ -16,6 +16,7 @@ export function MachineLearningDashboardEmbeddablesProvider( const retry = getService('retry'); const testSubjects = getService('testSubjects'); const find = getService('find'); + // const ml = getService('ml'); const dashboardAddPanel = getService('dashboardAddPanel'); const PageObjects = getPageObjects(['discover']); @@ -26,6 +27,22 @@ export function MachineLearningDashboardEmbeddablesProvider( }); }, + async assertSingleMetricViewerEmbeddableInitializerExists() { + await retry.tryForTime(10 * 1000, async () => { + await testSubjects.existOrFail('mlSingleMetricViewerEmbeddableInitializer', { + timeout: 1000, + }); + }); + }, + + async assertSingleMetricViewerEmbeddableInitializerNotExists() { + await retry.tryForTime(10 * 1000, async () => { + await testSubjects.missingOrFail('mlSingleMetricViewerEmbeddableInitializer', { + timeout: 1000, + }); + }); + }, + async assertAnomalyChartsEmbeddableInitializerNotExists() { await retry.tryForTime(10 * 1000, async () => { await testSubjects.missingOrFail('mlAnomalyChartsEmbeddableInitializer', { timeout: 1000 }); @@ -46,9 +63,7 @@ export function MachineLearningDashboardEmbeddablesProvider( }); }, - async assertInitializerConfirmButtonEnabled() { - const subj = 'mlAnomalyChartsInitializerConfirmButton'; - + async assertInitializerConfirmButtonEnabled(subj: string) { await retry.tryForTime(60 * 1000, async () => { await testSubjects.existOrFail(subj); await testSubjects.isEnabled(subj); @@ -58,12 +73,21 @@ export function MachineLearningDashboardEmbeddablesProvider( async clickInitializerConfirmButtonEnabled() { const subj = 'mlAnomalyChartsInitializerConfirmButton'; await retry.tryForTime(60 * 1000, async () => { - await this.assertInitializerConfirmButtonEnabled(); + await this.assertInitializerConfirmButtonEnabled(subj); await testSubjects.clickWhenNotDisabledWithoutRetry(subj); await this.assertAnomalyChartsEmbeddableInitializerNotExists(); }); }, + async clickSingleMetricViewerInitializerConfirmButtonEnabled() { + const subj = 'mlSingleMetricViewerInitializerConfirmButton'; + await retry.tryForTime(60 * 1000, async () => { + await this.assertInitializerConfirmButtonEnabled(subj); + await testSubjects.clickWhenNotDisabledWithoutRetry(subj); + await this.assertSingleMetricViewerEmbeddableInitializerNotExists(); + }); + }, + async assertDashboardIsEmpty() { await retry.tryForTime(60 * 1000, async () => { await testSubjects.existOrFail('emptyDashboardWidget'); @@ -101,7 +125,7 @@ export function MachineLearningDashboardEmbeddablesProvider( }, async openAnomalyJobSelectionFlyout( - mlEmbeddableType: 'ml_anomaly_swimlane' | 'ml_anomaly_charts' + mlEmbeddableType: 'ml_anomaly_swimlane' | 'ml_anomaly_charts' | 'ml_single_metric_viewer' ) { await retry.tryForTime(60 * 1000, async () => { await dashboardAddPanel.clickEditorMenuButton(); diff --git a/x-pack/test/functional/services/ml/dashboard_job_selection_table.ts b/x-pack/test/functional/services/ml/dashboard_job_selection_table.ts index e571a92fd92c0..376c41eeab2f0 100644 --- a/x-pack/test/functional/services/ml/dashboard_job_selection_table.ts +++ b/x-pack/test/functional/services/ml/dashboard_job_selection_table.ts @@ -18,6 +18,7 @@ export function MachineLearningDashboardJobSelectionTableProvider({ }: FtrProviderContext) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); + const find = getService('find'); return { async assertJobSelectionTableExists(): Promise { @@ -71,6 +72,33 @@ export function MachineLearningDashboardJobSelectionTableProvider({ } }, + async assertRowRadioButtonCheckedState(jobId: string, expectedCheckState: boolean) { + const actualCheckState = await this.getRowRadioButtonCheckedState(jobId); + expect(actualCheckState).to.eql( + expectedCheckState, + `Table row for job '${jobId}' check state should be '${expectedCheckState}' (got '${actualCheckState}')` + ); + }, + + async getRowRadioButtonCheckedState(jobId: string): Promise { + const radioButtonInput = await find.byCssSelector( + `[data-test-subj="${jobId}-radio-button"] > input#${jobId}` + ); + const radioButtonChecked = await radioButtonInput.getAttribute('checked'); + return radioButtonChecked === 'true'; + }, + + async setRowRadioButtonState(jobId: string, expectCheckedState: boolean) { + const subj = this.rowSelector(jobId, `${jobId}-radio-button`); + const checkedState = await this.getRowRadioButtonCheckedState(jobId); + if (checkedState !== expectCheckedState) { + await retry.tryForTime(5 * 1000, async () => { + await testSubjects.clickWhenNotDisabledWithoutRetry(subj); + await this.assertRowRadioButtonCheckedState(jobId, expectCheckedState); + }); + } + }, + async assertApplyJobSelectionEnabled() { const subj = 'mlFlyoutJobSelectorButtonApply'; await testSubjects.existOrFail(subj);