[ES|QL] Sets the KQL/Lucene query on dataview to ES|QL transition (#206391)

## Summary

Closes https://github.com/elastic/kibana/issues/203368

Transitions the KQL / Lucene query while transitioning from dataview to
ES|QL mode


![meow](https://github.com/user-attachments/assets/f0572699-c515-4348-8cee-35fdb6545fa4)


### Checklist
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Stratoula Kalafateli 2025-01-15 10:41:11 +02:00 committed by GitHub
parent 10cc89182f
commit 098e8cb518
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 120 additions and 6 deletions

View file

@ -101,4 +101,79 @@ describe('getInitialESQLQuery', () => {
'FROM logs* | WHERE @custom_timestamp >= ?_tstart AND @custom_timestamp <= ?_tend | LIMIT 10'
);
});
it('should append a where clause correctly if there is no @timestamp in the index fields and a query is given', () => {
const fields = [
{
name: '@custom_timestamp',
displayName: '@custom_timestamp',
type: 'date',
scripted: false,
filterable: true,
aggregatable: true,
sortable: true,
},
{
name: 'message',
displayName: 'message',
type: 'string',
scripted: false,
filterable: false,
},
] as DataView['fields'];
const dataView = getDataView('logs*', fields, '@custom_timestamp');
expect(getInitialESQLQuery(dataView, { language: 'kuery', query: 'error' })).toBe(
'FROM logs* | WHERE @custom_timestamp >= ?_tstart AND @custom_timestamp <= ?_tend AND KQL("""error""") | LIMIT 10'
);
});
it('should append a where clause correctly if there is @timestamp in the index fields and a query is given', () => {
const fields = [
{
name: '@timestamp',
displayName: '@timestamp',
type: 'date',
scripted: false,
filterable: true,
aggregatable: true,
sortable: true,
},
{
name: 'message',
displayName: 'message',
type: 'string',
scripted: false,
filterable: false,
},
] as DataView['fields'];
const dataView = getDataView('logs*', fields, 'timestamp');
expect(getInitialESQLQuery(dataView, { language: 'lucene', query: 'error' })).toBe(
'FROM logs* | WHERE QSTR("""error""") | LIMIT 10'
);
});
it('should not append a where clause correctly if there is @timestamp in the index fields and no kql or lucene query is given', () => {
const fields = [
{
name: '@timestamp',
displayName: '@timestamp',
type: 'date',
scripted: false,
filterable: true,
aggregatable: true,
sortable: true,
},
{
name: 'message',
displayName: 'message',
type: 'string',
scripted: false,
filterable: false,
},
] as DataView['fields'];
const dataView = getDataView('logs*', fields, 'timestamp');
expect(getInitialESQLQuery(dataView, { language: 'unknown', query: 'error' })).toBe(
'FROM logs* | LIMIT 10'
);
});
});

View file

@ -8,6 +8,31 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import { type Query, escapeQuotes } from '@kbn/es-query';
const getFilterBySearchText = (query?: Query) => {
if (!query) {
return '';
}
const searchTextFunc =
query.language === 'kuery' ? 'KQL' : query.language === 'lucene' ? 'QSTR' : '';
if (searchTextFunc && query.query) {
const escapedQuery =
typeof query.query === 'string' && query.language === 'lucene'
? escapeQuotes(query.query)
: query.query;
return `${searchTextFunc}("""${escapedQuery}""")`;
}
return '';
};
const getFinalWhereClause = (timeFilter?: string, queryFilter?: string) => {
if (timeFilter && queryFilter) {
return ` | WHERE ${timeFilter} AND ${queryFilter}`;
}
return timeFilter || queryFilter ? ` | WHERE ${timeFilter || queryFilter}` : '';
};
/**
* Builds an ES|QL query for the provided dataView
@ -15,12 +40,16 @@ import type { DataView } from '@kbn/data-views-plugin/public';
* If there is no @timestamp and there is a dataView timeFieldName, we add the WHERE clause with the timeFieldName
* @param dataView
*/
export function getInitialESQLQuery(dataView: DataView): string {
export function getInitialESQLQuery(dataView: DataView, query?: Query): string {
const hasAtTimestampField = dataView?.fields?.getByName?.('@timestamp')?.type === 'date';
const timeFieldName = dataView?.timeFieldName;
const filterByTimeParams =
!hasAtTimestampField && timeFieldName
? ` | WHERE ${timeFieldName} >= ?_tstart AND ${timeFieldName} <= ?_tend`
? `${timeFieldName} >= ?_tstart AND ${timeFieldName} <= ?_tend`
: '';
return `FROM ${dataView.getIndexPattern()}${filterByTimeParams} | LIMIT 10`;
const filterBySearchText = getFilterBySearchText(query);
const whereClause = getFinalWhereClause(filterByTimeParams, filterBySearchText);
return `FROM ${dataView.getIndexPattern()}${whereClause} | LIMIT 10`;
}

View file

@ -774,9 +774,10 @@ describe('Test discover state actions', () => {
savedSearchWithQuery.searchSource.setField('filter', filters);
const { state } = await getState('/', { savedSearch: savedSearchWithQuery });
state.globalState?.set({ filters });
state.appState.set({ query });
await state.actions.transitionFromDataViewToESQL(dataViewMock);
expect(state.appState.getState().query).toStrictEqual({
esql: 'FROM the-data-view-title | LIMIT 10',
esql: 'FROM the-data-view-title | WHERE KQL("""foo: \'bar\'""") | LIMIT 10',
});
expect(state.globalState?.get?.()?.filters).toStrictEqual([]);
expect(state.appState.getState().filters).toStrictEqual([]);

View file

@ -25,7 +25,13 @@ import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { v4 as uuidv4 } from 'uuid';
import { merge } from 'rxjs';
import { getInitialESQLQuery } from '@kbn/esql-utils';
import { AggregateQuery, isOfAggregateQueryType, Query, TimeRange } from '@kbn/es-query';
import {
AggregateQuery,
isOfAggregateQueryType,
isOfQueryType,
Query,
TimeRange,
} from '@kbn/es-query';
import { isFunction } from 'lodash';
import { loadSavedSearch as loadSavedSearchFn } from './utils/load_saved_search';
import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search';
@ -386,7 +392,10 @@ export function getDiscoverStateContainer({
};
const transitionFromDataViewToESQL = (dataView: DataView) => {
const queryString = getInitialESQLQuery(dataView);
const appState = appStateContainer.get();
const { query } = appState;
const filterQuery = query && isOfQueryType(query) ? query : undefined;
const queryString = getInitialESQLQuery(dataView, filterQuery);
appStateContainer.update({
query: { esql: queryString },