mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ES|QL] Initialize with time named params for dataviews without @timestamp (#189367)
## Summary Closes https://github.com/elastic/kibana/issues/189032 For indices with no @timestamp, when the users are transitioning from the dataview mode to the ES|QL mode then we are defaulting to ``` from dataviewIndex | where dataviewTimefield <= ?end and dataviewTimefield >= ?start ```  which will : 1. Train the users for the named params 2. Enable the histogram 3. Enable the datepicker ### 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 --------- Co-authored-by: Julia Rechkunova <julia.rechkunova@gmail.com>
This commit is contained in:
parent
9189208b5e
commit
788e9b34dd
8 changed files with 133 additions and 25 deletions
|
@ -5,11 +5,98 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { getInitialESQLQuery } from './get_initial_esql_query';
|
||||
|
||||
const getDataView = (name: string, dataViewFields: DataView['fields'], timeFieldName?: string) => {
|
||||
dataViewFields.getByName = (fieldName: string) => {
|
||||
return dataViewFields.find((field) => field.name === fieldName);
|
||||
};
|
||||
return {
|
||||
id: `${name}-id`,
|
||||
title: name,
|
||||
metaFields: ['_index', '_score'],
|
||||
fields: dataViewFields,
|
||||
type: 'default',
|
||||
getName: () => name,
|
||||
getIndexPattern: () => name,
|
||||
getFieldByName: jest.fn((fieldName: string) => dataViewFields.getByName(fieldName)),
|
||||
timeFieldName,
|
||||
isPersisted: () => true,
|
||||
toSpec: () => ({}),
|
||||
toMinimalSpec: () => ({}),
|
||||
} as unknown as DataView;
|
||||
};
|
||||
|
||||
describe('getInitialESQLQuery', () => {
|
||||
it('should work correctly', () => {
|
||||
expect(getInitialESQLQuery('logs*')).toBe('FROM logs* | LIMIT 10');
|
||||
it('should NOT add the where clause if there is @timestamp in the index', () => {
|
||||
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)).toBe('FROM logs* | LIMIT 10');
|
||||
});
|
||||
|
||||
it('should NOT add the where clause if there is @timestamp in the index although the dataview timefielName is different', () => {
|
||||
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)).toBe('FROM logs* | LIMIT 10');
|
||||
});
|
||||
|
||||
it('should append a where clause correctly if there is no @timestamp in the index fields', () => {
|
||||
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)).toBe(
|
||||
'FROM logs* | WHERE @custom_timestamp >= ?start AND @custom_timestamp <= ?end | LIMIT 10'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,11 +5,20 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
|
||||
/**
|
||||
* Builds an ES|QL query for the provided index or index pattern
|
||||
* @param indexOrIndexPattern
|
||||
* Builds an ES|QL query for the provided dataView
|
||||
* If there is @timestamp field in the index, we don't add the WHERE clause
|
||||
* If there is no @timestamp and there is a dataView timeFieldName, we add the WHERE clause with the timeFieldName
|
||||
* @param dataView
|
||||
*/
|
||||
export function getInitialESQLQuery(indexOrIndexPattern: string): string {
|
||||
return `FROM ${indexOrIndexPattern} | LIMIT 10`;
|
||||
export function getInitialESQLQuery(dataView: DataView): string {
|
||||
const hasAtTimestampField = dataView?.fields?.getByName?.('@timestamp')?.type === 'date';
|
||||
const timeFieldName = dataView?.timeFieldName;
|
||||
const filterByTimeParams =
|
||||
!hasAtTimestampField && timeFieldName
|
||||
? ` | WHERE ${timeFieldName} >= ?start AND ${timeFieldName} <= ?end`
|
||||
: '';
|
||||
return `FROM ${dataView.getIndexPattern()}${filterByTimeParams} | LIMIT 10`;
|
||||
}
|
||||
|
|
|
@ -9,14 +9,14 @@
|
|||
import { DISCOVER_ESQL_LOCATOR } from '@kbn/deeplinks-analytics';
|
||||
import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import { SerializableRecord } from '@kbn/utility-types';
|
||||
import { getIndexForESQLQuery, getInitialESQLQuery } from '@kbn/esql-utils';
|
||||
import { getIndexForESQLQuery, getInitialESQLQuery, getESQLAdHocDataview } from '@kbn/esql-utils';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
|
||||
export type DiscoverESQLLocatorParams = SerializableRecord;
|
||||
|
||||
export interface DiscoverESQLLocatorDependencies {
|
||||
discoverAppLocator: LocatorPublic<SerializableRecord>;
|
||||
getIndices: DataViewsPublicPluginStart['getIndices'];
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
}
|
||||
|
||||
export type DiscoverESQLLocator = LocatorPublic<DiscoverESQLLocatorParams>;
|
||||
|
@ -27,10 +27,10 @@ export class DiscoverESQLLocatorDefinition implements LocatorDefinition<Discover
|
|||
constructor(protected readonly deps: DiscoverESQLLocatorDependencies) {}
|
||||
|
||||
public readonly getLocation = async () => {
|
||||
const { discoverAppLocator, getIndices } = this.deps;
|
||||
|
||||
const indexName = await getIndexForESQLQuery({ dataViews: { getIndices } });
|
||||
const esql = getInitialESQLQuery(indexName ?? '*');
|
||||
const { discoverAppLocator, dataViews } = this.deps;
|
||||
const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*';
|
||||
const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews);
|
||||
const esql = getInitialESQLQuery(dataView);
|
||||
|
||||
const params = {
|
||||
query: { esql },
|
||||
|
|
|
@ -377,7 +377,7 @@ function getLoadParamsForNewSearch(stateContainer: DiscoverStateContainer): {
|
|||
? {
|
||||
// reset to a default ES|QL query
|
||||
query: {
|
||||
esql: getInitialESQLQuery(prevDataView.getIndexPattern()),
|
||||
esql: getInitialESQLQuery(prevDataView),
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
|
|
@ -50,7 +50,7 @@ export const getESQLSearchProvider: (
|
|||
|
||||
const params = {
|
||||
query: {
|
||||
esql: getInitialESQLQuery(defaultDataView?.getIndexPattern()),
|
||||
esql: getInitialESQLQuery(defaultDataView),
|
||||
},
|
||||
dataViewSpec: defaultDataView?.toSpec(),
|
||||
};
|
||||
|
|
|
@ -278,7 +278,7 @@ export class DiscoverPlugin
|
|||
plugins.share?.url.locators.create(
|
||||
new DiscoverESQLLocatorDefinition({
|
||||
discoverAppLocator: this.locator,
|
||||
getIndices: plugins.dataViews.getIndices,
|
||||
dataViews: plugins.dataViews,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ export function ChangeDataView({
|
|||
Boolean(textBasedLanguage)
|
||||
);
|
||||
const [isTextLangTransitionModalVisible, setIsTextLangTransitionModalVisible] = useState(false);
|
||||
const [selectedDataViewId, setSelectedDataViewId] = useState(currentDataViewId);
|
||||
const [selectedDataView, setSelectedDataView] = useState<DataView | undefined>(undefined);
|
||||
|
||||
const kibana = useKibana<IUnifiedSearchPluginServices>();
|
||||
const { application, data, storage, dataViews, dataViewEditor, appName, usageCollection } =
|
||||
|
@ -117,6 +117,10 @@ export function ChangeDataView({
|
|||
adHocDataViews?.map(mapAdHocDataView) ?? [];
|
||||
|
||||
setDataViewsList(savedDataViewRefs.concat(adHocDataViewRefs));
|
||||
if (currentDataViewId) {
|
||||
const currentDataview = await data.dataViews.get(currentDataViewId, false);
|
||||
setSelectedDataView(currentDataview);
|
||||
}
|
||||
};
|
||||
fetchDataViews();
|
||||
}, [data, currentDataViewId, adHocDataViews, savedDataViews, isTextBasedLangSelected]);
|
||||
|
@ -308,7 +312,8 @@ export function ChangeDataView({
|
|||
isTextBasedLangSelected={isTextBasedLangSelected}
|
||||
setPopoverIsOpen={setPopoverIsOpen}
|
||||
onChangeDataView={async (newId) => {
|
||||
setSelectedDataViewId(newId);
|
||||
const currentDataview = await data.dataViews.get(newId, false);
|
||||
setSelectedDataView(currentDataview);
|
||||
setPopoverIsOpen(false);
|
||||
|
||||
if (isTextBasedLangSelected) {
|
||||
|
@ -348,7 +353,13 @@ export function ChangeDataView({
|
|||
color="success"
|
||||
size="s"
|
||||
fullWidth
|
||||
onClick={() => onTextBasedSubmit({ esql: getInitialESQLQuery(trigger.title!) })}
|
||||
onClick={() => {
|
||||
if (selectedDataView) {
|
||||
onTextBasedSubmit({
|
||||
esql: getInitialESQLQuery(selectedDataView),
|
||||
});
|
||||
}
|
||||
}}
|
||||
data-test-subj="select-text-based-language-panel"
|
||||
contentProps={{
|
||||
css: {
|
||||
|
@ -393,8 +404,8 @@ export function ChangeDataView({
|
|||
language: 'kuery',
|
||||
query: '',
|
||||
});
|
||||
if (selectedDataViewId) {
|
||||
onChangeDataView(selectedDataViewId);
|
||||
if (selectedDataView?.id) {
|
||||
onChangeDataView(selectedDataView?.id);
|
||||
}
|
||||
setTriggerLabel(trigger.label);
|
||||
if (shouldDismissModal) {
|
||||
|
@ -405,7 +416,7 @@ export function ChangeDataView({
|
|||
onChangeDataView,
|
||||
onTextLangQuerySubmit,
|
||||
onTransitionModalDismiss,
|
||||
selectedDataViewId,
|
||||
selectedDataView?.id,
|
||||
trigger.label,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
getIndexForESQLQuery,
|
||||
ENABLE_ESQL,
|
||||
getESQLQueryColumns,
|
||||
getInitialESQLQuery,
|
||||
} from '@kbn/esql-utils';
|
||||
import type { Datasource, Visualization } from '../../types';
|
||||
import type { LensPluginStartDependencies } from '../../plugin';
|
||||
|
@ -81,10 +82,10 @@ export async function executeCreateAction({
|
|||
setVisualizationMap(visualizationMap);
|
||||
}
|
||||
|
||||
const defaultIndex = dataView.getIndexPattern();
|
||||
const esqlQuery = getInitialESQLQuery(dataView);
|
||||
|
||||
const defaultEsqlQuery = {
|
||||
esql: `FROM ${defaultIndex} | LIMIT 10`,
|
||||
esql: esqlQuery,
|
||||
};
|
||||
|
||||
// For the suggestions api we need only the columns
|
||||
|
@ -93,7 +94,7 @@ export async function executeCreateAction({
|
|||
// all the table
|
||||
const abortController = new AbortController();
|
||||
const columns = await getESQLQueryColumns({
|
||||
esqlQuery: `from ${defaultIndex}`,
|
||||
esqlQuery,
|
||||
search: deps.data.search.search,
|
||||
signal: abortController.signal,
|
||||
timeRange: deps.data.query.timefilter.timefilter.getAbsoluteTime(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue