diff --git a/x-pack/plugins/data_visualizer/common/types/field_stats.ts b/x-pack/plugins/data_visualizer/common/types/field_stats.ts index ea6956479bfec..bf707775bedf4 100644 --- a/x-pack/plugins/data_visualizer/common/types/field_stats.ts +++ b/x-pack/plugins/data_visualizer/common/types/field_stats.ts @@ -93,8 +93,8 @@ export interface StringFieldStats { fieldName: string; isTopValuesSampled: boolean; topValues: Bucket[]; - topValuesSampleSize: number; - topValuesSamplerShardSize: number; + topValuesSampleSize?: number; + topValuesSamplerShardSize?: number; } export interface DateFieldStats { diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx index d1ee934f6f566..ae9731f280388 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx @@ -55,10 +55,16 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed, } = useDataVisualizerKibana(); if (stats === undefined || !stats.topValues) return null; - const { topValues, fieldName, sampleCount } = stats; + const { topValues: originalTopValues, fieldName, sampleCount } = stats; - if (topValues?.length === 0) return null; + if (originalTopValues?.length === 0) return null; const totalDocuments = stats.totalDocuments ?? sampleCount ?? 0; + + const topValues = originalTopValues.map((bucket) => ({ + ...bucket, + percent: + typeof bucket.percent === 'number' ? bucket.percent : bucket.doc_count / totalDocuments, + })); const topValuesOtherCountPercent = 1 - (topValues ? topValues.reduce((acc, bucket) => acc + bucket.percent, 0) : 0); const topValuesOtherCount = Math.floor(topValuesOtherCountPercent * (sampleCount ?? 0)); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/esql/limit_size.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/esql/limit_size.tsx index 2e84191521bdb..0eeb1fec33147 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/esql/limit_size.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/esql/limit_size.tsx @@ -12,6 +12,7 @@ import type { ESQLDefaultLimitSizeOption } from '../../../embeddables/grid_embed const options = [ { + 'data-test-subj': 'dvESQLLimitSize-5000', value: '5000', text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.limitSizeOptionLabel', { defaultMessage: '{limit} rows', @@ -19,6 +20,7 @@ const options = [ }), }, { + 'data-test-subj': 'dvESQLLimitSize-10000', value: '10000', text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.limitSizeOptionLabel', { defaultMessage: '{limit} rows', @@ -26,6 +28,7 @@ const options = [ }), }, { + 'data-test-subj': 'dvESQLLimitSize-100000', value: '100000', text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.limitSizeOptionLabel', { defaultMessage: '{limit} rows', @@ -33,6 +36,7 @@ const options = [ }), }, { + 'data-test-subj': 'dvESQLLimitSize-1000000', value: '1000000', text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.limitSizeOptionLabel', { defaultMessage: '{limit} rows', @@ -40,6 +44,7 @@ const options = [ }), }, { + 'data-test-subj': 'dvESQLLimitSize-none', value: 'none', text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.analyzeAll', { defaultMessage: 'Analyze all', @@ -62,6 +67,7 @@ export const ESQLDefaultLimitSizeSelect = ({ return ( ; if (results) { - const topValuesSampleSize = results?.reduce((acc: number, row) => acc + row[0], 0); - const terms = results.map((row) => ({ key: row[1], doc_count: row[0], - percent: row[0] / topValuesSampleSize, })); return { fieldName: field.name, topValues: terms, - topValuesSampleSize, - topValuesSamplerShardSize: topValuesSampleSize, isTopValuesSampled: false, } as StringFieldStats; } diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_numeric_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_numeric_field_stats.ts index ca8684499eb3c..2f0ea4d7b3070 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_numeric_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_numeric_field_stats.ts @@ -92,7 +92,7 @@ const getESQLNumericFieldStatsInChunk = async ({ const median = values[startIndex + numericAccessorMap.p50]; const percentiles = values - .slice(startIndex + numericAccessorMap.p0, startIndex + numericAccessorMap.p100) + .slice(startIndex + numericAccessorMap.p5, startIndex + numericAccessorMap.p100 + 1) .map((value: number) => ({ value })); const distribution = processDistributionData(percentiles, PERCENTILE_SPACING, min); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts index b5f26bbb89f0b..d78e286e88d31 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts @@ -45,9 +45,9 @@ export const getESQLExampleFieldValues = async ({ if (textFieldsResp) { return textFields.map((textField, idx) => { - const examples = (textFieldsResp.rawResponse.values as unknown[][]).map( - (row) => row[idx] - ); + const examples = [ + ...new Set((textFieldsResp.rawResponse.values as unknown[][]).map((row) => row[idx])), + ]; return { fieldName: textField.name, diff --git a/x-pack/test/functional/apps/ml/data_visualizer/esql_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/esql_data_visualizer.ts new file mode 100644 index 0000000000000..167dc03510797 --- /dev/null +++ b/x-pack/test/functional/apps/ml/data_visualizer/esql_data_visualizer.ts @@ -0,0 +1,321 @@ +/* + * 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 { ML_JOB_FIELD_TYPES } from '@kbn/ml-anomaly-utils'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { MetricFieldVisConfig, NonMetricFieldVisConfig } from './types'; +/* + * 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. + */ + +export interface TestData { + suiteTitle: string; + query: string; + rowsPerPage?: 10 | 25 | 50; + sourceIndexOrSavedSearch?: string; + expected: { + hasDocCountChart: boolean; + initialLimitSize?: string; + totalDocCountFormatted: string; + metricFields?: MetricFieldVisConfig[]; + nonMetricFields?: NonMetricFieldVisConfig[]; + emptyFields: string[]; + visibleMetricFieldsCount: number; + totalMetricFieldsCount: number; + populatedFieldsCount: number; + totalFieldsCount: number; + }; +} + +const esqlFarequoteData = { + suiteTitle: 'ES|QL farequote', + query: 'from `ft_farequote`', + sourceIndexOrSavedSearch: 'ft_farequote', + expected: { + hasDocCountChart: true, + initialLimitSize: '10,000 (100%)', + totalDocCountFormatted: '86,274', + metricFields: [ + { + fieldName: 'responsetime', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '86,274 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 11, + viewableInLens: false, + }, + ], + nonMetricFields: [ + { + fieldName: '@timestamp', + type: ML_JOB_FIELD_TYPES.DATE, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '86,274 (100%)', + exampleCount: 2, + viewableInLens: false, + }, + { + fieldName: '@version', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '86,274 (100%)', + viewableInLens: false, + }, + { + fieldName: '@version.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '86,274 (100%)', + viewableInLens: false, + }, + { + fieldName: 'airline', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 11, + docCountFormatted: '86,274 (100%)', + viewableInLens: false, + }, + { + fieldName: 'type', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '86,274 (100%)', + viewableInLens: false, + }, + { + fieldName: 'type.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '86,274 (100%)', + viewableInLens: false, + }, + ], + emptyFields: ['sourcetype'], + visibleMetricFieldsCount: 1, + totalMetricFieldsCount: 1, + populatedFieldsCount: 7, + totalFieldsCount: 8, + }, +}; +const esqlSampleLogData: TestData = { + suiteTitle: 'ES|QL module_sample_logs', + query: `from ft_module_sample_logs +| where bytes > 7000 and response.keyword == "200" +| eval bytes_kb = bytes/1000 +| stats max_bytes_kb = max(bytes_kb), min_machine_ram = min(machine.ram) by clientip, geo.coordinates`, + sourceIndexOrSavedSearch: 'ft_module_sample_logs', + expected: { + hasDocCountChart: false, + totalDocCountFormatted: '149', + metricFields: [ + { + fieldName: 'max_bytes_kb', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '143 (95.97%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 12, + viewableInLens: false, + }, + { + fieldName: 'min_machine_ram', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '143 (95.97%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 20, + viewableInLens: false, + }, + ], + nonMetricFields: [ + { + fieldName: 'geo.coordinates', + type: ML_JOB_FIELD_TYPES.GEO_POINT, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '143 (95.97%)', + exampleCount: 10, + viewableInLens: false, + }, + { + fieldName: 'clientip', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '143 (95.97%)', + exampleCount: 11, + viewableInLens: false, + }, + ], + emptyFields: [], + visibleMetricFieldsCount: 2, + totalMetricFieldsCount: 2, + populatedFieldsCount: 4, + totalFieldsCount: 4, + }, +}; + +export default function ({ getPageObject, getService }: FtrProviderContext) { + const headerPage = getPageObject('header'); + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + function runTests(testData: TestData) { + it(`${testData.suiteTitle} loads the ES|QL data visualizer page`, async () => { + // Start navigation from the base of the ML app. + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToDataESQLDataVisualizer(); + }); + + it('should show the ES|QL editor and top panels', async () => { + await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists(); + }); + + it(`${testData.suiteTitle} displays index details`, async () => { + await ml.dataVisualizer.setESQLQuery(testData.query); + + await ml.dataVisualizerTable.assertTableRowCount(0); + + await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); + await ml.dataVisualizerIndexBased.clickUseFullDataButton( + testData.expected.totalDocCountFormatted, + 'none' + ); + await headerPage.waitUntilLoadingHasFinished(); + + await ml.dataVisualizerIndexBased.assertTotalDocCountHeaderExist(); + + if (testData.expected.hasDocCountChart) { + await ml.dataVisualizerIndexBased.assertTotalDocCountChartExist(); + } + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} displays elements in the data visualizer table correctly` + ); + await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist(); + + if (testData.rowsPerPage) { + await ml.dataVisualizerTable.ensureNumRowsPerPage(testData.rowsPerPage); + } + + await ml.dataVisualizerIndexBased.assertFieldCountPanelExist(); + await ml.dataVisualizerIndexBased.assertMetricFieldsSummaryExist(); + await ml.dataVisualizerIndexBased.assertFieldsSummaryExist(); + await ml.dataVisualizerIndexBased.assertVisibleMetricFieldsCount( + testData.expected.visibleMetricFieldsCount + ); + await ml.dataVisualizerIndexBased.assertTotalMetricFieldsCount( + testData.expected.totalMetricFieldsCount + ); + await ml.dataVisualizerIndexBased.assertVisibleFieldsCount( + testData.expected.populatedFieldsCount + ); + await ml.dataVisualizerIndexBased.assertTotalFieldsCount(testData.expected.totalFieldsCount); + + await ml.testExecution.logTestStep('displays unpopulated fields correctly'); + await ml.dataVisualizerTable.setShowEmptyFieldsSwitchState( + true, + testData.expected.emptyFields + ); + }); + + it(`${testData.suiteTitle} updates data when limit size changes`, async () => { + if (testData.expected.initialLimitSize !== undefined) { + await ml.testExecution.logTestStep('shows analysis for 10,000 rows by default'); + for (const fieldRow of testData.expected.metricFields as Array< + Required + >) { + await ml.dataVisualizerTable.assertNumberFieldContents( + fieldRow.fieldName, + testData.expected.initialLimitSize, + undefined, + false, + false, + true + ); + } + } + + await ml.testExecution.logTestStep('sets limit size to Analyze all'); + await ml.dataVisualizer.setLimitSize('none'); + + await ml.testExecution.logTestStep('updates table with newly set limit size'); + for (const fieldRow of testData.expected.metricFields as Array< + Required + >) { + await ml.dataVisualizerTable.assertNumberFieldContents( + fieldRow.fieldName, + fieldRow.docCountFormatted, + undefined, + false, + false, + true + ); + } + + for (const fieldRow of testData.expected.nonMetricFields!) { + await ml.dataVisualizerTable.assertNonMetricFieldContents( + fieldRow.type, + fieldRow.fieldName!, + fieldRow.docCountFormatted, + fieldRow.exampleCount, + false, + false, + undefined + ); + } + }); + } + + describe('esql', function () { + this.tags(['ml']); + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/module_sample_logs'); + + await ml.testResources.setKibanaTimeZoneToUTC(); + + await ml.securityUI.loginAsMlPowerUser(); + }); + + describe('with farequote', function () { + runTests(esqlFarequoteData); + }); + + describe('with module_sample_logs ', function () { + runTests(esqlSampleLogData); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index.ts b/x-pack/test/functional/apps/ml/data_visualizer/index.ts index 2145267edcc39..e5b1cbab24809 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index.ts @@ -31,7 +31,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await ml.testResources.resetKibanaTimeZone(); }); - loadTestFile(require.resolve('./index_data_visualizer')); loadTestFile(require.resolve('./index_data_visualizer_random_sampler')); loadTestFile(require.resolve('./index_data_visualizer_filters')); @@ -41,5 +40,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./index_data_visualizer_data_view_management')); loadTestFile(require.resolve('./file_data_visualizer')); loadTestFile(require.resolve('./data_drift')); + loadTestFile(require.resolve('./esql_data_visualizer')); }); } diff --git a/x-pack/test/functional/apps/ml/data_visualizer/types.ts b/x-pack/test/functional/apps/ml/data_visualizer/types.ts index 3c243be210860..b5ff416706a6e 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/types.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/types.ts @@ -36,7 +36,9 @@ export interface TestData { size: number; expected: { field: string; docCountFormatted: string }; }>; + query?: string; expected: { + initialLimitSize?: string; filters?: Array<{ key: string; value: string; diff --git a/x-pack/test/functional/services/ml/data_visualizer.ts b/x-pack/test/functional/services/ml/data_visualizer.ts index cccdeaf07e4fc..560e189aaf576 100644 --- a/x-pack/test/functional/services/ml/data_visualizer.ts +++ b/x-pack/test/functional/services/ml/data_visualizer.ts @@ -11,6 +11,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function MachineLearningDataVisualizerProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const find = getService('find'); + const retry = getService('retry'); + const browser = getService('browser'); return { async assertDataVisualizerImportDataCardExists() { @@ -54,6 +57,10 @@ export function MachineLearningDataVisualizerProvider({ getService }: FtrProvide }')` ); }, + async navigateToESQLVisualizer() { + await testSubjects.click('mlDataVisualizerSelectESQLButton'); + await testSubjects.existOrFail('dataVisualizerIndexPage'); + }, async navigateToDataViewSelection() { await testSubjects.click('mlDataVisualizerSelectIndexButton'); @@ -64,5 +71,49 @@ export function MachineLearningDataVisualizerProvider({ getService }: FtrProvide await testSubjects.click('mlDataVisualizerUploadFileButton'); await testSubjects.existOrFail('dataVisualizerPageFileUpload'); }, + + async setESQLQuery( + query: string, + refreshOrUpdateBtnSelector: + | 'superDatePickerApplyTimeButton' + | 'mlDatePickerRefreshPageButton loaded' = 'mlDatePickerRefreshPageButton loaded' + ) { + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail(refreshOrUpdateBtnSelector); + const visibleText = await testSubjects.getVisibleText(refreshOrUpdateBtnSelector); + + expect(visibleText).to.eql('Refresh'); + + await testSubjects.existOrFail('kibanaCodeEditor'); + await find.setValueByClass('kibanaCodeEditor', query); + + const updatedVisibleText = await testSubjects.getVisibleText(refreshOrUpdateBtnSelector); + + expect(updatedVisibleText).to.eql('Update'); + + await testSubjects.click(refreshOrUpdateBtnSelector); + }); + }, + + async assertLimitSize(size: 5000 | 10000 | 100000 | 1000000 | 'none') { + await testSubjects.existOrFail(`dvESQLLimitSize-${size}`, { timeout: 1000 }); + }, + + async setLimitSize(size: 5000 | 10000 | 100000 | 1000000 | 'none') { + await retry.tryForTime(5000, async () => { + // escape popover + await browser.pressKeys(browser.keys.ESCAPE); + + // Once clicked, show list of options + await testSubjects.clickWhenNotDisabled('dvESQLLimitSizeSelect'); + for (const option of [5000, 10000, 100000, 1000000, 'none']) { + await testSubjects.existOrFail(`dvESQLLimitSize-${option}`, { timeout: 1000 }); + } + + // Once option selected, assert if limit size is updated + await testSubjects.click(`dvESQLLimitSize-${size}`); + await this.assertLimitSize(size); + }); + }, }; } diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts index 09566d96f12b6..f8e6a7aeea844 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts @@ -22,7 +22,8 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ type RandomSamplerOption = | 'dvRandomSamplerOptionOnAutomatic' | 'dvRandomSamplerOptionOnManual' - | 'dvRandomSamplerOptionOff'; + | 'dvRandomSamplerOptionOff' + | 'none'; return { async assertTimeRangeSelectorSectionExists() { @@ -47,7 +48,9 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ await testSubjects.clickWhenNotDisabledWithoutRetry('mlDatePickerButtonUseFullData'); await testSubjects.clickWhenNotDisabledWithoutRetry('superDatePickerApplyTimeButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - await this.setRandomSamplingOption(randomSamplerOption); + if (randomSamplerOption !== 'none') { + await this.setRandomSamplingOption(randomSamplerOption); + } await await this.assertTotalDocumentCount(expectedFormattedTotalDocCount); }); }, diff --git a/x-pack/test/functional/services/ml/data_visualizer_table.ts b/x-pack/test/functional/services/ml/data_visualizer_table.ts index 1ad3efd90aa51..d0d56da9091d0 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -388,8 +388,8 @@ export function MachineLearningDataVisualizerTableProvider( public async assertNumberFieldContents( fieldName: string, docCountFormatted: string, - topValuesCount: number, - viewableInLens: boolean, + topValuesCount?: number, + viewableInLens?: boolean, hasActionMenu = false, checkDistributionPreviewExist = true ) { @@ -402,10 +402,12 @@ export function MachineLearningDataVisualizerTableProvider( this.detailsSelector(fieldName, 'dataVisualizerNumberSummaryTable') ); - await testSubjects.existOrFail( - this.detailsSelector(fieldName, 'dataVisualizerFieldDataTopValues') - ); - await this.assertTopValuesCount(fieldName, topValuesCount); + if (topValuesCount !== undefined) { + await testSubjects.existOrFail( + this.detailsSelector(fieldName, 'dataVisualizerFieldDataTopValues') + ); + await this.assertTopValuesCount(fieldName, topValuesCount); + } if (checkDistributionPreviewExist) { await this.assertDistributionPreviewExist(fieldName); diff --git a/x-pack/test/functional/services/ml/navigation.ts b/x-pack/test/functional/services/ml/navigation.ts index e0136a7c311a2..b1f46923efc48 100644 --- a/x-pack/test/functional/services/ml/navigation.ts +++ b/x-pack/test/functional/services/ml/navigation.ts @@ -200,6 +200,10 @@ export function MachineLearningNavigationProvider({ await this.navigateToArea('~mlMainTab & ~dataVisualizer', 'mlPageDataVisualizerSelector'); }, + async navigateToDataESQLDataVisualizer() { + await this.navigateToArea('~mlMainTab & ~esqlDataVisualizer', 'dataVisualizerIndexPage'); + }, + async navigateToDataDrift() { await this.navigateToArea('~mlMainTab & ~dataDrift', 'mlPageDataDrift'); },