diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index 8cf067edecc42..ad4648eaa06fc 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -92,6 +92,7 @@ enabled: - src/platform/test/functional/apps/discover/context_awareness/config.ts - src/platform/test/functional/apps/discover/observability/config.ts - src/platform/test/functional/apps/discover/query_mode/config.ts + - src/platform/test/functional/apps/discover/query_mode_esql_default/config.ts - src/platform/test/functional/apps/discover/tabs/config.ts - src/platform/test/functional/apps/discover/tabs2/config.ts - src/platform/test/functional/apps/discover/tabs3/config.ts diff --git a/src/platform/plugins/shared/discover/public/__mocks__/services.ts b/src/platform/plugins/shared/discover/public/__mocks__/services.ts index 2b7a89a0ca37d..f4df32e1737e8 100644 --- a/src/platform/plugins/shared/discover/public/__mocks__/services.ts +++ b/src/platform/plugins/shared/discover/public/__mocks__/services.ts @@ -285,6 +285,7 @@ export function createDiscoverServicesMock(): DiscoverServices { discoverShared: discoverSharedPluginMock.createStartContract(), discoverFeatureFlags: { getCascadeLayoutEnabled: jest.fn(() => false), + getIsEsqlDefault: jest.fn(() => false), }, embeddableEditor: { isByValueEditor: jest.fn(() => false), diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_initial_app_state.test.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_initial_app_state.test.ts index 2c953ce93d5d8..66f0e22b045e0 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_initial_app_state.test.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_initial_app_state.test.ts @@ -489,6 +489,98 @@ describe('getInitialAppState', () => { }); }); + describe('when esql default is enabled', () => { + describe('when the query mode is unset', () => { + it('should return an esql initial query', () => { + // Given + const services = createDiscoverServicesMock(); + services.storage.get = jest.fn().mockReturnValue(undefined); + services.uiSettings.get = jest.fn().mockReturnValue(true); + services.discoverFeatureFlags.getIsEsqlDefault = jest.fn(() => true); + + // When + const appState = getInitialAppState({ + hasGlobalState: false, + initialUrlState: undefined, + persistedTab: undefined, + dataView: new DataView({ + spec: dataViewMock.toSpec(), + fieldFormats: {} as DataView['fieldFormats'], + }), + services, + }); + + // Then + expect(appState).toEqual( + expect.objectContaining({ + query: { esql: 'FROM the-data-view-title' }, + }) + ); + }); + + describe('when esql uiSetting is disabled', () => { + it('should return the default query', () => { + // Given + const services = createDiscoverServicesMock(); + services.storage.get = jest.fn().mockReturnValue(undefined); + services.uiSettings.get = jest.fn().mockReturnValue(false); + services.discoverFeatureFlags.getIsEsqlDefault = jest.fn(() => true); + services.data.query.queryString.getDefaultQuery = jest + .fn() + .mockReturnValue(defaultQuery); + + // When + const appState = getInitialAppState({ + hasGlobalState: false, + initialUrlState: undefined, + persistedTab: undefined, + dataView: new DataView({ + spec: dataViewMock.toSpec(), + fieldFormats: {} as DataView['fieldFormats'], + }), + services, + }); + + // Then + expect(appState).toEqual( + expect.objectContaining({ + query: defaultQuery, + }) + ); + }); + }); + + describe('when dataView is not a DataView instance', () => { + it('should return the default query', () => { + // Given + const services = createDiscoverServicesMock(); + services.storage.get = jest.fn().mockReturnValue(undefined); + services.uiSettings.get = jest.fn().mockReturnValue(true); + services.discoverFeatureFlags.getIsEsqlDefault = jest.fn(() => true); + services.data.query.queryString.getDefaultQuery = jest + .fn() + .mockReturnValue(defaultQuery); + + // When + const appState = getInitialAppState({ + hasGlobalState: false, + initialUrlState: undefined, + persistedTab: undefined, + dataView: dataViewMock, + services, + }); + + // Then + expect(appState).toEqual( + expect.objectContaining({ + query: defaultQuery, + }) + ); + }); + }); + }); + }); + describe.each([ { queryMode: 'esql', description: 'esql but esql is disabled' }, { queryMode: 'classic', description: 'classic' }, diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_initial_app_state.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_initial_app_state.ts index 9f525de0366f2..f15565b418514 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_initial_app_state.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_initial_app_state.ts @@ -105,13 +105,17 @@ function getDefaultQuery({ if (hasGlobalState || hasInitialUrlState) return initialUrlState?.query || services.data.query.queryString.getDefaultQuery(); - // Lastly fall back to the last selected query mode if available - const hasEsqlEnabled = services.uiSettings.get(ENABLE_ESQL); - + // If the last query mode used by the user was classic, just return the default query const queryMode = services.storage.get(DISCOVER_QUERY_MODE_KEY); - if (hasEsqlEnabled && queryMode === 'esql' && dataView instanceof DataView) + if (queryMode === 'classic') return services.data.query.queryString.getDefaultQuery(); + + // If the last query mode used by the user was esql, or if esql is default, return the initial esql query + const canUseEsql = services.uiSettings.get(ENABLE_ESQL) && dataView instanceof DataView; + const isEsqlDefault = services.discoverFeatureFlags.getIsEsqlDefault(); + if (canUseEsql && (queryMode === 'esql' || isEsqlDefault)) return { esql: getInitialESQLQuery(dataView, true) }; + // Lastly, fall back to classic if we can't use anything else return services.data.query.queryString.getDefaultQuery(); } diff --git a/src/platform/plugins/shared/discover/public/build_services.ts b/src/platform/plugins/shared/discover/public/build_services.ts index a3f18d9c1a6f5..24a20f8797fec 100644 --- a/src/platform/plugins/shared/discover/public/build_services.ts +++ b/src/platform/plugins/shared/discover/public/build_services.ts @@ -71,7 +71,10 @@ import type { DiscoverSingleDocLocator } from './application/doc/locator'; import type { DiscoverAppLocator } from '../common'; import type { ProfilesManager } from './context_awareness'; import type { DiscoverEBTManager } from './ebt_manager'; -import { CASCADE_LAYOUT_ENABLED_FEATURE_FLAG_KEY } from './constants'; +import { + CASCADE_LAYOUT_ENABLED_FEATURE_FLAG_KEY, + IS_ESQL_DEFAULT_FEATURE_FLAG_KEY, +} from './constants'; import { EmbeddableEditorService } from './plugin_imports/embeddable_editor_service'; /** @@ -89,6 +92,7 @@ export interface UrlTracker { export interface DiscoverFeatureFlags { getCascadeLayoutEnabled: () => boolean; + getIsEsqlDefault: () => boolean; } export interface DiscoverServices { @@ -201,6 +205,8 @@ export const buildServices = ({ discoverFeatureFlags: { getCascadeLayoutEnabled: () => core.featureFlags.getBooleanValue(CASCADE_LAYOUT_ENABLED_FEATURE_FLAG_KEY, false), + getIsEsqlDefault: () => + core.featureFlags.getBooleanValue(IS_ESQL_DEFAULT_FEATURE_FLAG_KEY, false), }, docLinks: core.docLinks, embeddable: plugins.embeddable, diff --git a/src/platform/plugins/shared/discover/public/constants.ts b/src/platform/plugins/shared/discover/public/constants.ts index 43f71de0a6154..af272e378e6f8 100644 --- a/src/platform/plugins/shared/discover/public/constants.ts +++ b/src/platform/plugins/shared/discover/public/constants.ts @@ -12,3 +12,4 @@ export const ADHOC_DATA_VIEW_RENDER_EVENT = 'ad_hoc_data_view'; export const SEARCH_SESSION_ID_QUERY_PARAM = 'searchSessionId'; export const CASCADE_LAYOUT_ENABLED_FEATURE_FLAG_KEY = 'discover.cascadeLayoutEnabled'; +export const IS_ESQL_DEFAULT_FEATURE_FLAG_KEY = 'discover.isEsqlDefault'; diff --git a/src/platform/test/functional/apps/discover/group4/_discover_fields_api.ts b/src/platform/test/functional/apps/discover/group4/_discover_fields_api.ts index 07cf8d51df4ce..4acc345d44e25 100644 --- a/src/platform/test/functional/apps/discover/group4/_discover_fields_api.ts +++ b/src/platform/test/functional/apps/discover/group4/_discover_fields_api.ts @@ -74,7 +74,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickRowToggle(); await discover.isShowingDocViewer(); await discover.clickDocViewerTab('doc_view_source'); - await discover.expectSourceViewerToExist(); + await discover.isInEsqlMode(); }); }); } diff --git a/src/platform/test/functional/apps/discover/query_mode/_default_query_mode.ts b/src/platform/test/functional/apps/discover/query_mode/_default_query_mode.ts index 0d088010b539b..314e0afd6eaf9 100644 --- a/src/platform/test/functional/apps/discover/query_mode/_default_query_mode.ts +++ b/src/platform/test/functional/apps/discover/query_mode/_default_query_mode.ts @@ -16,8 +16,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'unifiedSearch', ]); - const testSubjects = getService('testSubjects'); - describe('Default query mode', () => { afterEach(async () => { await discover.resetQueryMode(); @@ -31,7 +29,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(queryMode).to.be(null); // Go to discover and validate classic mode - await testSubjects.existOrFail('discover-dataView-switch-link'); + await discover.isInClassicMode(); }); }); @@ -45,7 +43,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // Reload the app and validate ES|QL mode is persisted await common.navigateToApp('discover', { path: '' }); - await discover.expectSourceViewerToExist(); + await discover.isInEsqlMode(); }); }); @@ -60,7 +58,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // Reload the app and validate classic mode is persisted await common.navigateToApp('discover', { path: '' }); - await testSubjects.existOrFail('discover-dataView-switch-link'); + await discover.isInClassicMode(); }); }); }); diff --git a/src/platform/test/functional/apps/discover/query_mode_esql_default/_default_query_mode.ts b/src/platform/test/functional/apps/discover/query_mode_esql_default/_default_query_mode.ts new file mode 100644 index 0000000000000..32757c0109b8a --- /dev/null +++ b/src/platform/test/functional/apps/discover/query_mode_esql_default/_default_query_mode.ts @@ -0,0 +1,65 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { discover, common, unifiedSearch } = getPageObjects([ + 'discover', + 'common', + 'unifiedSearch', + ]); + + describe('Default query mode', () => { + afterEach(async () => { + await discover.resetQueryMode(); + }); + + describe('when there is no default query mode set', () => { + it('should open Discover in ESQL mode', async () => { + // Validate that no default query mode is set + await common.navigateToApp('discover'); + const queryMode = await discover.getQueryMode(); + expect(queryMode).to.be(null); + + // Go to discover and validate ESQL mode + await discover.isInEsqlMode(); + }); + }); + + describe('when the user clicks ES|QL mode', () => { + it('should set the default mode to ES|QL', async () => { + // Go to discover and select ES|QL mode + await common.navigateToApp('discover'); + await unifiedSearch.switchToDataViewMode(); + await discover.selectTextBaseLang(); + const queryMode = await discover.getQueryMode(); + expect(queryMode).to.contain('esql'); + + // Reload the app and validate ES|QL mode is persisted + await common.navigateToApp('discover', { path: '' }); + await discover.isInEsqlMode(); + }); + }); + + describe('when the user clicks classic', () => { + it('should set the default mode to classic', async () => { + // Go to discover and select classic mode + await common.navigateToApp('discover'); + await unifiedSearch.switchToDataViewMode(); + const queryMode = await discover.getQueryMode(); + expect(queryMode).to.contain('classic'); + + // Reload the app and validate classic mode is persisted + await common.navigateToApp('discover', { path: '' }); + await discover.isInClassicMode(); + }); + }); + }); +} diff --git a/src/platform/test/functional/apps/discover/query_mode_esql_default/config.ts b/src/platform/test/functional/apps/discover/query_mode_esql_default/config.ts new file mode 100644 index 0000000000000..90ea18514f829 --- /dev/null +++ b/src/platform/test/functional/apps/discover/query_mode_esql_default/config.ts @@ -0,0 +1,27 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); + const kbnTestServer = functionalConfig.get('kbnTestServer'); + + return { + ...functionalConfig.getAll(), + kbnTestServer: { + ...kbnTestServer, + serverArgs: [ + ...kbnTestServer.serverArgs, + '--feature_flags.overrides.discover.isEsqlDefault=true', + ], + }, + testFiles: [require.resolve('.')], + }; +} diff --git a/src/platform/test/functional/apps/discover/query_mode_esql_default/index.ts b/src/platform/test/functional/apps/discover/query_mode_esql_default/index.ts new file mode 100644 index 0000000000000..bcf02163382b5 --- /dev/null +++ b/src/platform/test/functional/apps/discover/query_mode_esql_default/index.ts @@ -0,0 +1,31 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import type { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + + describe('discover/query_mode_esql_default', function () { + before(async function () { + await esArchiver.loadIfNeeded( + 'src/platform/test/functional/fixtures/es_archiver/logstash_functional' + ); + await browser.setWindowSize(1600, 1200); + }); + + after(async function unloadMakelogs() { + await esArchiver.unload( + 'src/platform/test/functional/fixtures/es_archiver/logstash_functional' + ); + }); + + loadTestFile(require.resolve('./_default_query_mode')); + }); +} diff --git a/src/platform/test/functional/page_objects/discover_page.ts b/src/platform/test/functional/page_objects/discover_page.ts index 9693cd9292e72..3c9e93d22f954 100644 --- a/src/platform/test/functional/page_objects/discover_page.ts +++ b/src/platform/test/functional/page_objects/discover_page.ts @@ -522,10 +522,14 @@ export class DiscoverPageObject extends FtrService { return this.dataGrid.clickDocViewerTab(id); } - public async expectSourceViewerToExist() { + public async isInEsqlMode() { return await this.find.byClassName('monaco-editor'); } + public async isInClassicMode() { + return await this.testSubjects.existOrFail('discover-dataView-switch-link'); + } + public async expectDocTableToBeLoaded() { const renderComplete = await this.testSubjects.getAttribute( 'discoverDocTable',