diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index fa7aee4e3c54c..f9328e89cd19e 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -48,7 +48,7 @@ export class DiscoverPageObject extends FtrService { await fieldSearch.clearValue(); } - public async saveSearch(searchName: string) { + public async saveSearch(searchName: string, saveAsNew?: boolean) { await this.clickSaveSearchButton(); // preventing an occasional flakiness when the saved object wasn't set and the form can't be submitted await this.retry.waitFor( @@ -59,6 +59,14 @@ export class DiscoverPageObject extends FtrService { return (await saveButton.getAttribute('disabled')) !== 'true'; } ); + + if (saveAsNew !== undefined) { + await this.retry.waitFor(`save as new switch is set`, async () => { + await this.testSubjects.setEuiSwitch('saveAsNewCheckbox', saveAsNew ? 'check' : 'uncheck'); + return (await this.testSubjects.isEuiSwitchChecked('saveAsNewCheckbox')) === saveAsNew; + }); + } + await this.testSubjects.click('confirmSaveSavedObjectButton'); await this.header.waitUntilLoadingHasFinished(); // LeeDr - this additional checking for the saved search name was an attempt 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 c1e5d0b4b6aae..3bb8e3d728318 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index.ts @@ -13,6 +13,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./index_data_visualizer')); loadTestFile(require.resolve('./index_data_visualizer_grid_in_discover')); + loadTestFile(require.resolve('./index_data_visualizer_grid_in_dashboard')); loadTestFile(require.resolve('./index_data_visualizer_actions_panel')); loadTestFile(require.resolve('./index_data_visualizer_index_pattern_management')); loadTestFile(require.resolve('./file_data_visualizer')); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index ff0d489293682..bcdf978d46cad 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { TestData, MetricFieldVisConfig } from './types'; import { farequoteDataViewTestData, + farequoteKQLFiltersSearchTestData, farequoteKQLSearchTestData, farequoteLuceneSearchTestData, sampleLogTestData, @@ -76,6 +77,13 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.dataVisualizerIndexBased.assertTotalFieldsCount(testData.expected.totalFieldsCount); + if (testData.expected.filters) { + await ml.testExecution.logTestStep('displays filters in filter bar correctly'); + for (const filter of testData.expected.filters!) { + await ml.dataVisualizerIndexBased.assertFilterBarFilterContent(filter); + } + } + await ml.testExecution.logTestStep( 'displays details for metric fields and non-metric fields correctly' ); @@ -96,7 +104,9 @@ export default function ({ getService }: FtrProviderContext) { fieldRow.fieldName!, fieldRow.docCountFormatted, fieldRow.exampleCount, - fieldRow.viewableInLens + fieldRow.viewableInLens, + false, + fieldRow.exampleContent ); } @@ -150,6 +160,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.testResources.createIndexPatternIfNeeded('ft_module_sample_logs', '@timestamp'); await ml.testResources.createSavedSearchFarequoteLuceneIfNeeded(); await ml.testResources.createSavedSearchFarequoteKueryIfNeeded(); + await ml.testResources.createSavedSearchFarequoteFilterAndKueryIfNeeded(); await ml.testResources.setKibanaTimeZoneToUTC(); await ml.securityUI.loginAsMlPowerUser(); @@ -182,6 +193,14 @@ export default function ({ getService }: FtrProviderContext) { }); runTests(farequoteLuceneSearchTestData); + + it(`${farequoteKQLFiltersSearchTestData.suiteTitle} loads the data visualizer selector page`, async () => { + // Start navigation from the base of the ML app. + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToDataVisualizer(); + }); + + runTests(farequoteKQLFiltersSearchTestData); }); describe('with module_sample_logs ', function () { diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts new file mode 100644 index 0000000000000..97c6c06bc3225 --- /dev/null +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts @@ -0,0 +1,122 @@ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; +import { TestData, MetricFieldVisConfig } from './types'; +import { farequoteLuceneFiltersSearchTestData } from './index_test_data'; + +const SHOW_FIELD_STATISTICS = 'discover:showFieldStatistics'; +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'settings', 'dashboard']); + const ml = getService('ml'); + const retry = getService('retry'); + const dashboardAddPanel = getService('dashboardAddPanel'); + + function runTests(testData: TestData) { + const savedSearchTitle = `Field stats for ${testData.suiteTitle} ${Date.now()}`; + const dashboardTitle = `Dashboard for ${testData.suiteTitle} ${Date.now()}`; + const startTime = 'Jan 1, 2016 @ 00:00:00.000'; + const endTime = 'Nov 1, 2020 @ 00:00:00.000'; + + describe(`with ${testData.suiteTitle}`, function () { + after(async function () { + await ml.testResources.deleteSavedSearchByTitle(savedSearchTitle); + await ml.testResources.deleteDashboardByTitle(dashboardTitle); + }); + + it(`saves search with Field statistics table in Discover`, async function () { + await ml.testResources.setAdvancedSettingProperty(SHOW_FIELD_STATISTICS, true); + + await PageObjects.common.navigateToApp('discover'); + if (testData.isSavedSearch) { + await retry.tryForTime(2 * 1000, async () => { + await PageObjects.discover.loadSavedSearch(testData.sourceIndexOrSavedSearch); + }); + } else { + await ml.dashboardEmbeddables.selectDiscoverIndexPattern( + testData.sourceIndexOrSavedSearch + ); + } + await PageObjects.timePicker.setAbsoluteRange(startTime, endTime); + + await PageObjects.discover.assertViewModeToggleExists(); + await PageObjects.discover.clickViewModeFieldStatsButton(); + await ml.testExecution.logTestStep('saves as new search'); + await PageObjects.discover.saveSearch(savedSearchTitle, true); + }); + + it(`displays Field statistics table in Dashboard when enabled`, async function () { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.addSavedSearch(savedSearchTitle); + await PageObjects.dashboard.waitForRenderComplete(); + + await PageObjects.timePicker.setAbsoluteRange(startTime, endTime); + await PageObjects.dashboard.waitForRenderComplete(); + + for (const fieldRow of testData.expected.metricFields as Array< + Required + >) { + await ml.dataVisualizerTable.assertNumberFieldContents( + fieldRow.fieldName, + fieldRow.docCountFormatted, + fieldRow.topValuesCount, + fieldRow.viewableInLens + ); + } + + for (const fieldRow of testData.expected.nonMetricFields!) { + await ml.dataVisualizerTable.assertNonMetricFieldContents( + fieldRow.type, + fieldRow.fieldName!, + fieldRow.docCountFormatted, + fieldRow.exampleCount, + fieldRow.viewableInLens, + false, + fieldRow.exampleContent + ); + } + + await PageObjects.dashboard.saveDashboard(dashboardTitle); + }); + + it(`doesn't display Field statistics table in Dashboard when disabled`, async function () { + await ml.testResources.setAdvancedSettingProperty(SHOW_FIELD_STATISTICS, false); + + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.gotoDashboardEditMode(dashboardTitle); + await PageObjects.dashboard.waitForRenderComplete(); + + await dashboardAddPanel.addSavedSearch(savedSearchTitle); + await PageObjects.dashboard.waitForRenderComplete(); + + await PageObjects.timePicker.setAbsoluteRange(startTime, endTime); + await PageObjects.dashboard.waitForRenderComplete(); + + await PageObjects.discover.assertFieldStatsTableNotExists(); + await PageObjects.dashboard.saveDashboard(dashboardTitle); + }); + }); + } + + describe('field statistics in Dashboard', function () { + before(async function () { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.createSavedSearchFarequoteFilterAndLuceneIfNeeded(); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async function () { + await ml.testResources.clearAdvancedSettingProperty(SHOW_FIELD_STATISTICS); + }); + + runTests(farequoteLuceneFiltersSearchTestData); + }); +} diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_discover.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_discover.ts index ba24684e13036..fff2c6fdbf232 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_discover.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_discover.ts @@ -5,7 +5,6 @@ * 2.0. */ -import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { TestData, MetricFieldVisConfig } from './types'; @@ -22,39 +21,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'settings']); const ml = getService('ml'); - const testSubjects = getService('testSubjects'); const retry = getService('retry'); - const toasts = getService('toasts'); - const selectIndexPattern = async (indexPattern: string) => { - await retry.tryForTime(2 * 1000, async () => { - await PageObjects.discover.selectIndexPattern(indexPattern); - const indexPatternTitle = await testSubjects.getVisibleText('indexPattern-switch-link'); - expect(indexPatternTitle).to.be(indexPattern); - }); - }; - - const clearAdvancedSetting = async (propertyName: string) => { - await retry.tryForTime(2 * 1000, async () => { - await PageObjects.common.navigateToUrl('management', 'kibana/settings', { - shouldUseHashForSubUrl: false, - }); - if ((await PageObjects.settings.getAdvancedSettingCheckbox(propertyName)) === 'true') { - await PageObjects.settings.clearAdvancedSettings(propertyName); - } - }); - }; - - const setAdvancedSettingCheckbox = async (propertyName: string, checkedState: boolean) => { - await retry.tryForTime(2 * 1000, async () => { - await PageObjects.common.navigateToUrl('management', 'kibana/settings', { - shouldUseHashForSubUrl: false, - }); - await testSubjects.click('settings'); - await toasts.dismissAllToasts(); - await PageObjects.settings.toggleAdvancedSettingCheckbox(propertyName, checkedState); - }); - }; + const startTime = 'Jan 1, 2016 @ 00:00:00.000'; + const endTime = 'Nov 1, 2020 @ 00:00:00.000'; function runTestsWhenDisabled(testData: TestData) { it('should not show view mode toggle or Field stats table', async function () { @@ -64,13 +34,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.loadSavedSearch(testData.sourceIndexOrSavedSearch); }); } else { - await selectIndexPattern(testData.sourceIndexOrSavedSearch); + await ml.dashboardEmbeddables.selectDiscoverIndexPattern(testData.sourceIndexOrSavedSearch); } - await PageObjects.timePicker.setAbsoluteRange( - 'Jan 1, 2016 @ 00:00:00.000', - 'Nov 1, 2020 @ 00:00:00.000' - ); + await PageObjects.timePicker.setAbsoluteRange(startTime, endTime); await PageObjects.discover.assertViewModeToggleNotExists(); await PageObjects.discover.assertFieldStatsTableNotExists(); @@ -86,12 +53,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.loadSavedSearch(testData.sourceIndexOrSavedSearch); }); } else { - await selectIndexPattern(testData.sourceIndexOrSavedSearch); + await ml.dashboardEmbeddables.selectDiscoverIndexPattern( + testData.sourceIndexOrSavedSearch + ); } - await PageObjects.timePicker.setAbsoluteRange( - 'Jan 1, 2016 @ 00:00:00.000', - 'Nov 1, 2020 @ 00:00:00.000' - ); + await PageObjects.timePicker.setAbsoluteRange(startTime, endTime); await PageObjects.discover.assertHitCount(testData.expected.totalDocCountFormatted); await PageObjects.discover.assertViewModeToggleExists(); @@ -140,16 +106,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async function () { - await clearAdvancedSetting(SHOW_FIELD_STATISTICS); + await ml.testResources.clearAdvancedSettingProperty(SHOW_FIELD_STATISTICS); }); describe('when enabled', function () { before(async function () { - await setAdvancedSettingCheckbox(SHOW_FIELD_STATISTICS, true); + await ml.testResources.setAdvancedSettingProperty(SHOW_FIELD_STATISTICS, true); }); after(async function () { - await clearAdvancedSetting(SHOW_FIELD_STATISTICS); + await ml.testResources.clearAdvancedSettingProperty(SHOW_FIELD_STATISTICS); }); runTests(farequoteDataViewTestData); @@ -163,7 +129,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('when disabled', function () { before(async function () { // Ensure that the setting is set to default state which is false - await setAdvancedSettingCheckbox(SHOW_FIELD_STATISTICS, false); + await ml.testResources.setAdvancedSettingProperty(SHOW_FIELD_STATISTICS, false); }); runTestsWhenDisabled(farequoteDataViewTestData); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_test_data.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_test_data.ts index 6dd782487fdf8..b4279e9c2c440 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_test_data.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_test_data.ts @@ -213,6 +213,7 @@ export const farequoteKQLFiltersSearchTestData: TestData = { { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, ], expected: { + filters: [{ key: 'airline', value: 'ASA' }], totalDocCountFormatted: '5,674', metricFields: [ { @@ -408,6 +409,7 @@ export const farequoteLuceneFiltersSearchTestData: TestData = { { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, ], expected: { + filters: [{ key: 'airline', value: 'ASA' }], totalDocCountFormatted: '5,673', metricFields: [ { 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 5c3f890dba561..dc38bc31f568b 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/types.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/types.ts @@ -33,6 +33,13 @@ export interface TestData { expected: { field: string; docCountFormatted: string }; }>; expected: { + filters?: Array<{ + key: string; + value: string; + enabled?: boolean; + pinned?: boolean; + negated?: boolean; + }>; totalDocCountFormatted: string; metricFields?: MetricFieldVisConfig[]; nonMetricFields?: NonMetricFieldVisConfig[]; diff --git a/x-pack/test/functional/services/ml/dashboard_embeddables.ts b/x-pack/test/functional/services/ml/dashboard_embeddables.ts index 5c55a16698cb6..1eb4e25a4edd5 100644 --- a/x-pack/test/functional/services/ml/dashboard_embeddables.ts +++ b/x-pack/test/functional/services/ml/dashboard_embeddables.ts @@ -10,13 +10,14 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { MlDashboardJobSelectionTable } from './dashboard_job_selection_table'; export function MachineLearningDashboardEmbeddablesProvider( - { getService }: FtrProviderContext, + { getService, getPageObjects }: FtrProviderContext, mlDashboardJobSelectionTable: MlDashboardJobSelectionTable ) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); const find = getService('find'); const dashboardAddPanel = getService('dashboardAddPanel'); + const PageObjects = getPageObjects(['discover']); return { async assertAnomalyChartsEmbeddableInitializerExists() { @@ -112,5 +113,13 @@ export function MachineLearningDashboardEmbeddablesProvider( await mlDashboardJobSelectionTable.assertJobSelectionTableExists(); }); }, + + async selectDiscoverIndexPattern(indexPattern: string) { + await retry.tryForTime(2 * 1000, async () => { + await PageObjects.discover.selectIndexPattern(indexPattern); + const indexPatternTitle = await testSubjects.getVisibleText('indexPattern-switch-link'); + expect(indexPatternTitle).to.be(indexPattern); + }); + }, }; } 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 6883946452629..82ec33452a5b9 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 @@ -16,6 +16,7 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ const retry = getService('retry'); const PageObjects = getPageObjects(['discover']); const queryBar = getService('queryBar'); + const filterBar = getService('filterBar'); return { async assertTimeRangeSelectorSectionExists() { @@ -208,5 +209,27 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ ); }); }, + + async assertFilterBarFilterContent(filter: { + key: string; + value: string; + enabled?: boolean; + pinned?: boolean; + negated?: boolean; + }) { + await retry.waitForWithTimeout( + `filter ${JSON.stringify(filter)} to exist`, + 2000, + async () => { + return await filterBar.hasFilter( + filter.key, + filter.value, + filter.enabled, + filter.pinned, + filter.negated + ); + } + ); + }, }; } diff --git a/x-pack/test/functional/services/ml/security_common.ts b/x-pack/test/functional/services/ml/security_common.ts index 925565143bda0..7af8312855357 100644 --- a/x-pack/test/functional/services/ml/security_common.ts +++ b/x-pack/test/functional/services/ml/security_common.ts @@ -144,7 +144,9 @@ export function MachineLearningSecurityCommonProvider({ getService }: FtrProvide // such as "View in Lens", "Add to Dashboard", and creating anomaly detection rules. These feature privileges are the minimal ones // necessary to satisfy all of those functional tests. feature: { - discover: ['read'], + // FIXME: We need permission to save search in Discover to test the data viz embeddable + // change permission back to read once tests are moved out of ML + discover: ['all'], visualize: ['read'], dashboard: ['all'], actions: ['all'], diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index 071db63125a55..affd317d22e81 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -509,5 +509,18 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider log.debug(` > found version '${packageVersion}'`); return packageVersion; }, + + async setAdvancedSettingProperty( + propertyName: string, + propertyValue: string | number | boolean + ) { + await kibanaServer.uiSettings.update({ + [propertyName]: propertyValue, + }); + }, + + async clearAdvancedSettingProperty(propertyName: string) { + await kibanaServer.uiSettings.unset(propertyName); + }, }; }