Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .buildkite/ftr_platform_stateful_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ enabled:
- src/platform/test/functional/apps/discover/group10/config.ts
- 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/tabs/config.ts
- src/platform/test/functional/apps/discover/tabs2/config.ts
- src/platform/test/functional/apps/discover/tabs3/config.ts
Expand Down
3 changes: 3 additions & 0 deletions src/platform/plugins/shared/discover/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const getDefaultRowsPerPage = (uiSettings: IUiSettingsClient): number =>
// local storage key for the ES|QL to Dataviews transition modal
export const ESQL_TRANSITION_MODAL_KEY = 'data.textLangTransitionModal';

// local storage key for the query mode when starting a new discover session
export const DISCOVER_QUERY_MODE_KEY = 'discover.defaultQueryMode';

/**
* The id value used to indicate that a link should open in a new Discover tab.
* It will be used in the `_tab` URL param to indicate that a new tab should be created.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
selectAllTabs,
} from '../../state_management/redux';
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants';
import {
DISCOVER_QUERY_MODE_KEY,
ESQL_TRANSITION_MODAL_KEY,
} from '../../../../../common/constants';
import { useTopNavMenuItems } from '../top_nav/use_top_nav_menu_items';
import { isEsqlSource } from '../../../../../common/data_sources';

Expand Down Expand Up @@ -98,6 +101,7 @@ export const useAppMenuData = ({ currentDataView }: UseAppMenuDataParams): UseAp
} else {
dispatch(transitionFromESQLToDataView({ dataViewId: currentDataView?.id ?? '' }));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the transition modal to Classic can be dismissed, the value should not change to classic in this case.

Screenshot 2026-02-03 at 13 36 29

Then a single place in the code, where to move services.storage.set(DISCOVER_QUERY_MODE_KEY, 'classic'); to, might be inside transitionFromESQLToDataView action. Or better as a redux listener to the action.

For consistency we could apply a similar change to transitionFromESQLToDataView.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that makes sense, i thought of that at first but i wasn't sure if we wanted to introduce a side-effect in the redux actions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to redux listener in 5a4c68b - it seems to be working but it's my first time doing one redux listener so I hope it's the way it should be

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant listeners for transitionFromESQLToDataView and transitionFromDataViewToESQL actions. We still want to capture only the manual changes to the mode.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into it!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i got it now a135bf0 🙈

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wasn't sure if we wanted to introduce a side-effect in the redux actions

Just wanted to say this is totally fine to do in thunks and middleware, often preferable even. It keeps the logic consolidated and makes it easy to test.

Personally in your case I'd put the logic directly in the thunk to avoid the extra actions indirection, but middleware works too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm happy with either, i've seen now that i can access the storage in the thunks - no strong opinion from my side

}
services.storage.set(DISCOVER_QUERY_MODE_KEY, 'classic');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to clear the previously saved value then so when we continue with #250038 it would not interfere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean to instead of save which of the modes was the last one we just store if the last one was ESQL? i guess the problem then is going to be that once ESQL is the default in #250201 if someone switches back to classic it won't use it as the next default

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, good point! Then we just need to keep in mind that if users had switched to classic before we enabled ES|QL by default then they will continue using the classic mode without noticing the change in Discover cc @kertal

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx for raising, yes, also didn't think about this. so it makes sense, to store it with classic, but I think further down the road, we might think of solving it differently, because as @jughosta highlighted, one we generally enable ES|QL as default, those users still would see classic ... worth a comment in the code, so it doesn't get lost

},
},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useGetRuleTypesPermissions } from '@kbn/alerts-ui-shared';
import useObservable from 'react-use/lib/useObservable';
import type { DiscoverSession } from '@kbn/saved-search-plugin/common';
import { useI18n } from '@kbn/i18n-react';
import { DISCOVER_QUERY_MODE_KEY } from '../../../../../common/constants';
import type { DiscoverAppLocatorParams } from '../../../../../common';
import { createDataViewDataSource } from '../../../../../common/data_sources';
import type { DiscoverServices } from '../../../../build_services';
Expand Down Expand Up @@ -232,6 +233,7 @@ export const useTopNavLinks = ({
}),
run: () => {
if (dataView) {
services.storage.set(DISCOVER_QUERY_MODE_KEY, 'esql');
Comment thread
jughosta marked this conversation as resolved.
Outdated
dispatch(transitionFromDataViewToESQL({ dataView }));
services.trackUiMetric?.(METRIC_TYPE.CLICK, `esql:try_btn_clicked`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,111 @@ describe('getInitialAppState', () => {
`);
});

describe('when there is no persistedTab', () => {
describe('when there is a query in the url state', () => {
it('should use the query from the url state', () => {
// Given
const services = createDiscoverServicesMock();
const query = { language: 'kuery', query: 'url state query' };

// When
const appState = getInitialAppState({
initialUrlState: { query },
persistedTab: undefined,
dataView: dataViewMock,
services,
});

// Then
expect(appState).toEqual(
expect.objectContaining({
query,
})
);
});
});

describe('when there is no query in the url state', () => {
describe('when there is a data source in the url state', () => {
it('should use the default query', () => {
// Given
const services = createDiscoverServicesMock();
const dataSource = createDataViewDataSource({ dataViewId: 'some-data-view-id' });
services.data.query.queryString.getDefaultQuery = jest.fn().mockReturnValue(defaultQuery);

// When
const appState = getInitialAppState({
initialUrlState: { dataSource },
persistedTab: undefined,
dataView: dataViewMock,
services,
});

// Then
expect(appState).toEqual(
expect.objectContaining({
query: defaultQuery,
})
);
});
});

describe('when there is no data source in the url state', () => {
describe('when the query mode is esql', () => {
it('should return an esql initial query', () => {
// Given
const services = createDiscoverServicesMock();
services.storage.get = jest.fn().mockReturnValue('esql');

// When
const appState = getInitialAppState({
initialUrlState: undefined,
persistedTab: undefined,
dataView: dataViewMock,
services,
});

// Then
expect(appState).toEqual(
expect.objectContaining({
query: { esql: 'FROM the-data-view-title' },
})
);
});
});

describe.each([
{ queryMode: 'classic', description: 'classic' },
{ queryMode: undefined, description: 'unset' },
])('when the query mode is $description', ({ queryMode }) => {
it('should return the default query', () => {
// Given
const services = createDiscoverServicesMock();
services.storage.get = jest.fn().mockReturnValue(queryMode);
services.data.query.queryString.getDefaultQuery = jest
.fn()
.mockReturnValue(defaultQuery);

// When
const appState = getInitialAppState({
initialUrlState: undefined,
persistedTab: undefined,
dataView: dataViewMock,
services,
});

// Then
expect(appState).toEqual(
expect.objectContaining({
query: defaultQuery,
})
);
});
});
});
});
});

describe('default sort array', () => {
test('should use persistedTab sort array if valid and data view is provided', () => {
const services = createDiscoverServicesMock();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import type { DataView } from '@kbn/data-views-plugin/common';
import type { AggregateQuery, Query } from '@kbn/es-query';
import { isOfAggregateQueryType } from '@kbn/es-query';
import type { DiscoverSessionTab } from '@kbn/saved-search-plugin/common';
import type { IUiSettingsClient } from '@kbn/core/public';
Expand All @@ -20,6 +21,8 @@ import {
} from '@kbn/discover-utils';
import { getChartHidden } from '@kbn/unified-histogram';
import { cloneDeep } from 'lodash';
import { getInitialESQLQuery } from '@kbn/esql-utils';
import { DISCOVER_QUERY_MODE_KEY } from '../../../../../common/constants';
import type { DiscoverServices } from '../../../../build_services';
import type { DiscoverAppState } from '../redux';
import {
Expand All @@ -45,6 +48,7 @@ export function getInitialAppState({
persistedTab,
dataView,
services,
initialUrlState,
});
const mergedState = { ...defaultAppState, ...initialUrlState };

Expand Down Expand Up @@ -77,18 +81,54 @@ function getDefaultColumns(
: undefined;
}

function isDataView(dataView: unknown): dataView is DataView {
return !!dataView && typeof dataView === 'object' && 'getIndexPattern' in dataView;
}

function getDefaultQuery({
initialUrlState,
persistedTab,
storage,
data,
dataView,
}: {
persistedTab: DiscoverSessionTab | undefined;
storage: DiscoverServices['storage'];
data: DiscoverServices['data'];
dataView: DataView | Pick<DataView, 'id' | 'timeFieldName'> | undefined;
initialUrlState: DiscoverAppState | undefined;
}): Query | AggregateQuery | undefined {
if (persistedTab?.serializedSearchSource.query) return persistedTab.serializedSearchSource.query;

// This scenarios are used when we are adding a new tab:
// 1. We only have the query set if:
// a. The tab is in ES|QL mode
// b. The tab is in other mode but has some filter in place
// 2. We have no query set but we do have a data source if we are in classic mode with no filters
if (initialUrlState?.query) return initialUrlState.query;
if (initialUrlState?.dataSource) return data.query.queryString.getDefaultQuery();
Comment thread
jughosta marked this conversation as resolved.
Outdated

// Fallback to last query mode for new sessions
const queryMode = storage.get(DISCOVER_QUERY_MODE_KEY);
if (queryMode === 'esql' && isDataView(dataView))
Comment thread
jughosta marked this conversation as resolved.
Outdated
return { esql: getInitialESQLQuery(dataView, true) };
Comment thread
jughosta marked this conversation as resolved.

return data.query.queryString.getDefaultQuery();
}

function getDefaultAppState({
persistedTab,
dataView,
services,
initialUrlState,
}: {
persistedTab: DiscoverSessionTab | undefined;
dataView: DataView | Pick<DataView, 'id' | 'timeFieldName'> | undefined;
services: DiscoverServices;
initialUrlState: DiscoverAppState | undefined;
}) {
const { data, uiSettings, storage } = services;
const query =
persistedTab?.serializedSearchSource.query || data.query.queryString.getDefaultQuery();
const query = getDefaultQuery({ persistedTab, storage, data, dataView, initialUrlState });
Comment thread
jughosta marked this conversation as resolved.
Outdated
const isEsqlQuery = isOfAggregateQueryType(query);
// If the data view doesn't have a getFieldByName method (e.g. if it's a spec or list item),
// we assume the sort array is valid since we can't know for sure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {

describe('trackSubmittingQuery telemetry', () => {
beforeEach(async () => {
await discover.resetQueryMode();
await common.navigateToApp('discover');
await discover.waitUntilTabIsLoaded();
await ebtUIHelper.setOptIn(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await discover.waitUntilSearchingHasFinished();
});

afterEach(async function () {
await discover.resetQueryMode();
});

it('should show histogram by default', async () => {
await checkHistogramVis(defaultTimespan, defaultTotalCount);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
beforeEach(async function () {
await timePicker.setDefaultAbsoluteRangeViaUiSettings();
await kibanaServer.uiSettings.update(defaultSettings);
await discover.resetQueryMode();
await common.navigateToApp('discover');
await discover.waitUntilSearchingHasFinished();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await discover.waitUntilSearchingHasFinished();
});

afterEach(async function () {
await discover.resetQueryMode();
});

it('should show highlights for in-table search', async () => {
expect(await dataGrid.getCurrentPageNumber()).to.be('1');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await kibanaServer.savedObjects.cleanStandardList();
});

beforeEach(async function () {
await discover.resetQueryMode();
});

it('saves the last URL when in data view mode', async function () {
await common.navigateToApp('discover');
await header.waitUntilLoadingHasFinished();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 ({ getPageObjects, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');

const { discover, common } = getPageObjects(['discover', 'common']);

describe('Default query mode', () => {
afterEach(async () => {
await discover.resetQueryMode();
});

beforeEach(async () => {
await common.navigateToApp('discover');
});

describe('when the default query mode is ES|QL', () => {
it('should open Discover in ES|QL mode', async () => {
await discover.setQueryMode('esql');
await common.navigateToApp('discover');
await discover.expectSourceViewerToExist();
});
});

describe('when the default query mode is classic', () => {
it('should open Discover in classic mode', async () => {
await discover.setQueryMode('classic');
await common.navigateToApp('discover');
await testSubjects.existOrFail('discover-dataView-switch-link');
});
});

describe('when the default query mode is unset', () => {
it('should open Discover in classic mode', async () => {
await discover.resetQueryMode();
await common.navigateToApp('discover');
await testSubjects.existOrFail('discover-dataView-switch-link');
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to also add a test when the path is not empty.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the unit tests are kind of covering this scenario, but i can follow up in the feature flag PR with more tests if it doesn't look that way

});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 }: FtrProviderContext) {
const { discover, common } = getPageObjects(['discover', 'common']);

describe('Update query mode', () => {
afterEach(async () => {
await discover.resetQueryMode();
});

describe('when the user clicks ES|QL mode', () => {
it('should set the default mode to ES|QL', async () => {
await common.navigateToApp('discover');
await discover.selectTextBaseLang();
const queryMode = await discover.getQueryMode();
expect(queryMode).to.contain('esql');
});
});

describe('when the user clicks classic', () => {
it('should set the default mode to classic', async () => {
await common.navigateToApp('discover');
await discover.selectTextBaseLang();
await discover.selectDataViewMode();
const queryMode = await discover.getQueryMode();
expect(queryMode).to.contain('classic');
});
});
});
}
Comment thread
jughosta marked this conversation as resolved.
Outdated
Loading
Loading