[8.13] [Discover] Fix "New" link in ES|QL mode (#177038) (#177894)

# Backport

This will backport the following commits from `main` to `8.13`:
- [[Discover] Fix "New" link in ES|QL mode
(#177038)](https://github.com/elastic/kibana/pull/177038)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Julia
Rechkunova","email":"julia.rechkunova@elastic.co"},"sourceCommit":{"committedDate":"2024-02-23T11:10:29Z","message":"[Discover]
Fix \"New\" link in ES|QL mode (#177038)\n\n- Closes
https://github.com/elastic/kibana/issues/176873\r\n\r\n##
Summary\r\n\r\nThis PR makes sure that when user presses \"New\" top nav
link in\r\nDiscover, they will stay in ES|QL mode if the previous mode
was ES|QL\r\ntoo. The ES|QL query will be reset to the initial one `from
<index\r\npattern> | limit 10`. For this query I created a new
util\r\n`getInitialESQLQuery()`.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"a78661217702684133417ca926bc411bf1f1beae","branchLabelMapping":{"^v8.14.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport
missing","Team:DataDiscovery","backport:prev-minor","Feature:ES|QL","v8.14.0"],"number":177038,"url":"https://github.com/elastic/kibana/pull/177038","mergeCommit":{"message":"[Discover]
Fix \"New\" link in ES|QL mode (#177038)\n\n- Closes
https://github.com/elastic/kibana/issues/176873\r\n\r\n##
Summary\r\n\r\nThis PR makes sure that when user presses \"New\" top nav
link in\r\nDiscover, they will stay in ES|QL mode if the previous mode
was ES|QL\r\ntoo. The ES|QL query will be reset to the initial one `from
<index\r\npattern> | limit 10`. For this query I created a new
util\r\n`getInitialESQLQuery()`.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"a78661217702684133417ca926bc411bf1f1beae"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.14.0","labelRegex":"^v8.14.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/177038","number":177038,"mergeCommit":{"message":"[Discover]
Fix \"New\" link in ES|QL mode (#177038)\n\n- Closes
https://github.com/elastic/kibana/issues/176873\r\n\r\n##
Summary\r\n\r\nThis PR makes sure that when user presses \"New\" top nav
link in\r\nDiscover, they will stay in ES|QL mode if the previous mode
was ES|QL\r\ntoo. The ES|QL query will be reset to the initial one `from
<index\r\npattern> | limit 10`. For this query I created a new
util\r\n`getInitialESQLQuery()`.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"a78661217702684133417ca926bc411bf1f1beae"}}]}]
BACKPORT-->

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Julia Rechkunova 2024-03-04 15:30:47 +01:00 committed by GitHub
parent e71112b4fd
commit 9bcca4780c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 219 additions and 13 deletions

View file

@ -13,4 +13,5 @@ export {
getLimitFromESQLQuery,
removeDropCommandsFromESQLQuery,
getIndexForESQLQuery,
getInitialESQLQuery,
} from './src';

View file

@ -7,6 +7,7 @@
*/
export { getESQLAdHocDataview, getIndexForESQLQuery } from './utils/get_esql_adhoc_dataview';
export { getInitialESQLQuery } from './utils/get_initial_esql_query';
export {
getIndexPatternFromSQLQuery,
getIndexPatternFromESQLQuery,

View file

@ -0,0 +1,15 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { getInitialESQLQuery } from './get_initial_esql_query';
describe('getInitialESQLQuery', () => {
it('should work correctly', () => {
expect(getInitialESQLQuery('logs*')).toBe('from logs* | limit 10');
});
});

View file

@ -0,0 +1,15 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
/**
* Builds an ES|QL query for the provided index or index pattern
* @param indexOrIndexPattern
*/
export function getInitialESQLQuery(indexOrIndexPattern: string): string {
return `from ${indexOrIndexPattern} | limit 10`;
}

View file

@ -9,7 +9,7 @@
import { DISCOVER_ESQL_LOCATOR } from '@kbn/deeplinks-analytics';
import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/common';
import { SerializableRecord } from '@kbn/utility-types';
import { getIndexForESQLQuery } from '@kbn/esql-utils';
import { getIndexForESQLQuery, getInitialESQLQuery } from '@kbn/esql-utils';
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
export type DiscoverESQLLocatorParams = SerializableRecord;
@ -30,7 +30,7 @@ export class DiscoverESQLLocatorDefinition implements LocatorDefinition<Discover
const { discoverAppLocator, getIndices } = this.deps;
const indexName = await getIndexForESQLQuery({ dataViews: { getIndices } });
const esql = `from ${indexName ?? '*'} | limit 10`;
const esql = getInitialESQLQuery(indexName ?? '*');
const params = {
query: { esql },

View file

@ -20,6 +20,8 @@ import useObservable from 'react-use/lib/useObservable';
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
import { withSuspense } from '@kbn/shared-ux-utility';
import { isOfEsqlQueryType } from '@kbn/es-query';
import { getInitialESQLQuery } from '@kbn/esql-utils';
import { ESQL_TYPE } from '@kbn/data-view-utils';
import { useUrl } from './hooks/use_url';
import { useDiscoverStateContainer } from './hooks/use_discover_state_container';
import { MainHistoryLocationState } from '../../../common';
@ -38,6 +40,8 @@ import {
} from '../../customizations';
import type { DiscoverCustomizationContext } from '../types';
import { DiscoverTopNavServerless } from './components/top_nav/discover_topnav_serverless';
import { isTextBasedQuery } from './utils/is_text_based_query';
import { DiscoverStateContainer, LoadParams } from './services/discover_state';
const DiscoverMainAppMemoized = memo(DiscoverMainApp);
@ -148,7 +152,10 @@ export function DiscoverMainRoute({
}, [data.dataViews, savedSearchId, stateContainer.appState]);
const loadSavedSearch = useCallback(
async (nextDataView?: DataView) => {
async ({
nextDataView,
initialAppState,
}: { nextDataView?: DataView; initialAppState?: LoadParams['initialAppState'] } = {}) => {
const loadSavedSearchStartTime = window.performance.now();
setLoading(true);
if (!nextDataView && !(await checkData())) {
@ -162,6 +169,7 @@ export function DiscoverMainRoute({
savedSearchId,
dataView: nextDataView,
dataViewSpec: historyLocationState?.dataViewSpec,
initialAppState,
});
if (customizationContext.displayMode === 'standalone') {
if (currentSavedSearch?.id) {
@ -230,8 +238,12 @@ export function DiscoverMainRoute({
setHasUserDataView(false);
setShowNoDataPage(false);
setError(undefined);
// restore the previously selected data view for a new state
loadSavedSearch(!savedSearchId ? stateContainer.internalState.getState().dataView : undefined);
if (savedSearchId) {
loadSavedSearch();
} else {
// restore the previously selected data view for a new state (when a saved search was open)
loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
}
}, [isCustomizationServiceInitialized, loadSavedSearch, savedSearchId, stateContainer]);
// secondary fetch: in case URL is set to `/`, used to reset to 'new' state, keeping the current data view
@ -240,8 +252,7 @@ export function DiscoverMainRoute({
savedSearchId,
onNewUrl: () => {
// restore the previously selected data view for a new state
const dataView = stateContainer.internalState.getState().dataView;
loadSavedSearch(dataView);
loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
},
});
@ -251,7 +262,7 @@ export function DiscoverMainRoute({
setLoading(true);
setShowNoDataPage(false);
setError(undefined);
await loadSavedSearch(nextDataView as DataView);
await loadSavedSearch({ nextDataView: nextDataView as DataView });
}
},
[loadSavedSearch]
@ -351,3 +362,27 @@ export function DiscoverMainRoute({
}
// eslint-disable-next-line import/no-default-export
export default DiscoverMainRoute;
function getLoadParamsForNewSearch(stateContainer: DiscoverStateContainer): {
nextDataView: LoadParams['dataView'];
initialAppState: LoadParams['initialAppState'];
} {
const prevAppState = stateContainer.appState.getState();
const prevDataView = stateContainer.internalState.getState().dataView;
const initialAppState =
prevAppState?.query &&
isTextBasedQuery(prevAppState.query) &&
prevDataView &&
prevDataView.type === ESQL_TYPE
? {
// reset to a default ES|QL query
query: {
esql: getInitialESQLQuery(prevDataView.getIndexPattern()),
},
}
: undefined;
return {
nextDataView: prevDataView,
initialAppState,
};
}

View file

@ -87,6 +87,10 @@ export interface LoadParams {
* the data view to use, if undefined, the saved search's data view will be used
*/
dataView?: DataView;
/**
* Custom initial app state for loading a saved search
*/
initialAppState?: DiscoverAppState;
/**
* the data view spec to use, if undefined, the saved search's data view will be used
*/

View file

@ -45,7 +45,7 @@ export const loadSavedSearch = async (
deps: LoadSavedSearchDeps
): Promise<SavedSearch> => {
addLog('[discoverState] loadSavedSearch');
const { savedSearchId } = params ?? {};
const { savedSearchId, initialAppState } = params ?? {};
const {
appStateContainer,
internalStateContainer,
@ -54,7 +54,7 @@ export const loadSavedSearch = async (
services,
} = deps;
const appStateExists = !appStateContainer.isEmptyURL();
const appState = appStateExists ? appStateContainer.getState() : undefined;
const appState = appStateExists ? appStateContainer.getState() : initialAppState;
// Loading the saved search or creating a new one
let nextSavedSearch = savedSearchId

View file

@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import type { GlobalSearchResultProvider } from '@kbn/global-search-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { getInitialESQLQuery } from '@kbn/esql-utils';
import type { DiscoverAppLocator } from '../../common';
/**
@ -49,7 +50,7 @@ export const getESQLSearchProvider: (
const params = {
query: {
esql: `from ${defaultDataView?.getIndexPattern()} | limit 10`,
esql: getInitialESQLQuery(defaultDataView?.getIndexPattern()),
},
dataViewSpec: defaultDataView?.toSpec(),
};

View file

@ -84,7 +84,8 @@
"@kbn/esql-utils",
"@kbn/managed-content-badge",
"@kbn/deeplinks-analytics",
"@kbn/shared-ux-markdown"
"@kbn/shared-ux-markdown",
"@kbn/data-view-utils"
],
"exclude": ["target/**/*"]
}

View file

@ -30,6 +30,7 @@ import {
import { METRIC_TYPE } from '@kbn/analytics';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { AggregateQuery, getLanguageDisplayName } from '@kbn/es-query';
import { getInitialESQLQuery } from '@kbn/esql-utils';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { IUnifiedSearchPluginServices } from '../types';
import { type DataViewPickerPropsExtended } from './data_view_picker';
@ -341,7 +342,7 @@ export function ChangeDataView({
color="success"
size="s"
fullWidth
onClick={() => onTextBasedSubmit({ esql: `from ${trigger.title} | limit 10` })}
onClick={() => onTextBasedSubmit({ esql: getInitialESQLQuery(trigger.title!) })}
data-test-subj="select-text-based-language-panel"
contentProps={{
css: {

View file

@ -45,6 +45,7 @@
"@kbn/code-editor",
"@kbn/calculate-width-from-char-count",
"@kbn/react-kibana-context-render",
"@kbn/esql-utils",
],
"exclude": [
"target/**/*",

View file

@ -0,0 +1,130 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const PageObjects = getPageObjects([
'common',
'discover',
'timePicker',
'header',
'unifiedSearch',
]);
const kibanaServer = getService('kibanaServer');
const filterBar = getService('filterBar');
const queryBar = getService('queryBar');
const monacoEditor = getService('monacoEditor');
const testSubjects = getService('testSubjects');
const security = getService('security');
describe('discover new search action', function () {
before(async function () {
await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json');
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' });
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setDefaultAbsoluteRange();
});
after(async () => {
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover.json');
await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
await kibanaServer.uiSettings.replace({});
await kibanaServer.savedObjects.cleanStandardList();
});
it('should work correctly for data view mode', async function () {
await filterBar.addFilter({ field: 'extension', operation: 'is', value: 'png' });
await PageObjects.header.waitUntilLoadingHasFinished();
await queryBar.setQuery('bytes > 15000');
await queryBar.submitQuery();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await PageObjects.discover.getHitCount()).to.be('353');
expect(await filterBar.hasFilter('extension', 'png')).to.be(true);
expect(await queryBar.getQueryString()).to.be('bytes > 15000');
await PageObjects.discover.clickNewSearchButton();
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await PageObjects.discover.getHitCount()).to.be('14,004');
expect(await filterBar.hasFilter('extension', 'png')).to.be(false);
expect(await queryBar.getQueryString()).to.be('');
});
it('should work correctly for a saved search in data view mode', async function () {
await PageObjects.discover.createAdHocDataView('logs*', true);
await filterBar.addFilter({ field: 'extension', operation: 'is', value: 'css' });
await PageObjects.header.waitUntilLoadingHasFinished();
await queryBar.setQuery('bytes > 100');
await queryBar.submitQuery();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await PageObjects.discover.getHitCount()).to.be('2,108');
expect(await filterBar.hasFilter('extension', 'css')).to.be(true);
expect(await queryBar.getQueryString()).to.be('bytes > 100');
await PageObjects.discover.saveSearch('adHoc');
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await PageObjects.discover.getHitCount()).to.be('2,108');
await PageObjects.discover.clickNewSearchButton();
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await PageObjects.discover.getHitCount()).to.be('14,004');
expect(await filterBar.hasFilter('extension', 'css')).to.be(false);
expect(await queryBar.getQueryString()).to.be('');
expect(
await PageObjects.unifiedSearch.getSelectedDataView('discover-dataView-switch-link')
).to.be('logs**');
expect(await PageObjects.discover.isAdHocDataViewSelected()).to.be(true);
});
it('should work correctly for ESQL mode', async () => {
await PageObjects.discover.selectTextBaseLang();
const testQuery = `from logstash-* | limit 100 | stats countB = count(bytes) by geo.dest | sort countB`;
await monacoEditor.setCodeEditorValue(testQuery);
await testSubjects.click('querySubmitButton');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await PageObjects.discover.getHitCountInt()).to.greaterThan(10);
await testSubjects.existOrFail('unifiedHistogramSuggestionSelector');
await PageObjects.discover.clickNewSearchButton();
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await monacoEditor.getCodeEditorValue()).to.be('from logstash-* | limit 10');
await testSubjects.missingOrFail('unifiedHistogramSuggestionSelector'); // histogram also updated
expect(await PageObjects.discover.getHitCount()).to.be('10');
});
it('should work correctly for a saved search in ESQL mode', async () => {
await PageObjects.discover.selectTextBaseLang();
const testQuery = `from logstash-* | limit 100 | stats countB = count(bytes) by geo.dest | sort countB`;
await monacoEditor.setCodeEditorValue(testQuery);
await testSubjects.click('querySubmitButton');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await PageObjects.discover.getHitCountInt()).to.greaterThan(10);
await testSubjects.existOrFail('unifiedHistogramSuggestionSelector');
await PageObjects.discover.saveSearch('esql');
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await PageObjects.discover.getHitCountInt()).to.greaterThan(10);
await PageObjects.discover.clickNewSearchButton();
await PageObjects.discover.waitUntilSearchingHasFinished();
expect(await monacoEditor.getCodeEditorValue()).to.be('from logstash-* | limit 10');
await testSubjects.missingOrFail('unifiedHistogramSuggestionSelector'); // histogram also updated
expect(await PageObjects.discover.getHitCount()).to.be('10');
});
});
}

View file

@ -35,5 +35,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_data_view_edit'));
loadTestFile(require.resolve('./_field_list_new_fields'));
loadTestFile(require.resolve('./_request_cancellation'));
loadTestFile(require.resolve('./_new_search'));
});
}