diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index 8fd84f1a6a2a6..30e654ef98eef 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -5,6 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import './discover_layout.scss'; import React, { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { @@ -23,7 +24,7 @@ import { METRIC_TYPE } from '@kbn/analytics'; import classNames from 'classnames'; import { generateFilters } from '@kbn/data-plugin/public'; import { useDragDropContext } from '@kbn/dom-drag-drop'; -import { DataViewField, DataViewType } from '@kbn/data-views-plugin/public'; +import { DataViewType } from '@kbn/data-views-plugin/public'; import { SEARCH_FIELDS_FROM_SOURCE, SHOW_FIELD_STATISTICS, @@ -145,8 +146,11 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { }); }, [dataView, isEsqlMode, observabilityAIAssistant?.service]); - const onAddFilter = useCallback( - (field: DataViewField | string, values: unknown, operation: '+' | '-') => { + const onAddFilter = useCallback( + (field, values, operation) => { + if (!field) { + return; + } const fieldName = typeof field === 'string' ? field : field.name; popularizeField(dataView, fieldName, dataViews, capabilities); const newFilters = generateFilters(filterManager, field, values, operation, dataView); @@ -158,36 +162,35 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { [filterManager, dataView, dataViews, trackUiMetric, capabilities] ); - const onPopulateWhereClause = useCallback( - (field: DataViewField | string, values: unknown, operation: '+' | '-') => { - if (query && isOfAggregateQueryType(query) && 'esql' in query) { - const fieldName = typeof field === 'string' ? field : field.name; - // send the field type for casting - const fieldType = typeof field !== 'string' ? field.type : undefined; - // weird existence logic from Discover components - // in the field it comes the operator _exists_ and in the value the field - // I need to take care of it here but I think it should be handled on the fieldlist instead - const updatedQuery = appendWhereClauseToESQLQuery( - query.esql, - fieldName === '_exists_' ? String(values) : fieldName, - fieldName === '_exists_' ? undefined : values, - fieldName === '_exists_' ? '_exists_' : operation, - fieldType - ); - data.query.queryString.setQuery({ - esql: updatedQuery, - }); - if (trackUiMetric) { - trackUiMetric(METRIC_TYPE.CLICK, 'esql_filter_added'); - } + const onPopulateWhereClause = useCallback( + (field, values, operation) => { + if (!field || !isOfAggregateQueryType(query)) { + return; + } + const fieldName = typeof field === 'string' ? field : field.name; + // send the field type for casting + const fieldType = typeof field !== 'string' ? field.type : undefined; + // weird existence logic from Discover components + // in the field it comes the operator _exists_ and in the value the field + // I need to take care of it here but I think it should be handled on the fieldlist instead + const updatedQuery = appendWhereClauseToESQLQuery( + query.esql, + fieldName === '_exists_' ? String(values) : fieldName, + fieldName === '_exists_' ? undefined : values, + fieldName === '_exists_' ? '_exists_' : operation, + fieldType + ); + data.query.queryString.setQuery({ + esql: updatedQuery, + }); + if (trackUiMetric) { + trackUiMetric(METRIC_TYPE.CLICK, 'esql_filter_added'); } }, [data.query.queryString, query, trackUiMetric] ); - const onFilter = isEsqlMode - ? (onPopulateWhereClause as DocViewFilterFn) - : (onAddFilter as DocViewFilterFn); + const onFilter = isEsqlMode ? onPopulateWhereClause : onAddFilter; const onFieldEdited = useCallback( async ({ removedFieldName }: { removedFieldName?: string } = {}) => { diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx index cafa5e8baf806..fa679fa00ecef 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx @@ -10,13 +10,7 @@ import React, { useCallback, useState } from 'react'; import { css } from '@emotion/react'; import { EuiEmptyPrompt, EuiButton, EuiSpacer, useEuiTheme } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/common'; -import { - isOfQueryType, - isOfAggregateQueryType, - type Query, - type AggregateQuery, - type Filter, -} from '@kbn/es-query'; +import { isOfQueryType, type Query, type AggregateQuery, type Filter } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { NoResultsSuggestionDefault } from './no_results_suggestion_default'; @@ -50,8 +44,8 @@ export const NoResultsSuggestions: React.FC = ({ const { euiTheme } = useEuiTheme(); const services = useDiscoverServices(); const { data, uiSettings, timefilter, toastNotifications } = services; - const hasQuery = - (isOfQueryType(query) && !!query?.query) || (!!query && isOfAggregateQueryType(query)); + const isEsqlMode = useIsEsqlMode(); + const hasQuery = Boolean(isOfQueryType(query) && query.query) || isEsqlMode; const hasFilters = hasActiveFilter(filters); const [timeRangeExtendingStatus, setTimeRangeExtendingStatus] = @@ -132,8 +126,6 @@ export const NoResultsSuggestions: React.FC = ({ ); - const isEsqlMode = useIsEsqlMode(); - return ( void; }) => { + const appState = state.appState.getState(); const currentTitle = savedSearch.title; const currentTimeRestore = savedSearch.timeRestore; const currentRowsPerPage = savedSearch.rowsPerPage; @@ -121,18 +122,19 @@ export async function onSaveSearch({ const currentDescription = savedSearch.description; const currentTags = savedSearch.tags; const currentVisContext = savedSearch.visContext; + savedSearch.title = newTitle; savedSearch.description = newDescription; savedSearch.timeRestore = newTimeRestore; savedSearch.rowsPerPage = isLegacyTableEnabled({ uiSettings, - isEsqlMode: isOfAggregateQueryType(savedSearch.searchSource.getField('query')), + isEsqlMode: isDataSourceType(appState.dataSource, DataSourceType.Esql), }) ? currentRowsPerPage - : state.appState.getState().rowsPerPage; + : appState.rowsPerPage; // save the custom value or reset it if it's invalid - const appStateSampleSize = state.appState.getState().sampleSize; + const appStateSampleSize = appState.sampleSize; const allowedSampleSize = getAllowedSampleSize(appStateSampleSize, uiSettings); savedSearch.sampleSize = appStateSampleSize && allowedSampleSize === appStateSampleSize @@ -165,6 +167,7 @@ export async function onSaveSearch({ state, navigateOrReloadSavedSearch, }); + // If the save wasn't successful, put the original values back. if (!response) { savedSearch.title = currentTitle; @@ -180,7 +183,9 @@ export async function onSaveSearch({ state.internalState.transitions.resetOnSavedSearchChange(); state.appState.resetInitialState(); } + onSaveCb?.(); + return response; }; @@ -199,6 +204,7 @@ export async function onSaveSearch({ onClose={onClose ?? (() => {})} /> ); + showSaveModal(saveModal); } diff --git a/src/plugins/discover/public/application/main/data_fetching/fetch_all.ts b/src/plugins/discover/public/application/main/data_fetching/fetch_all.ts index aa0da72e6ff7a..3e42be009f36c 100644 --- a/src/plugins/discover/public/application/main/data_fetching/fetch_all.ts +++ b/src/plugins/discover/public/application/main/data_fetching/fetch_all.ts @@ -5,6 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import { Adapters } from '@kbn/inspector-plugin/common'; import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; import { BehaviorSubject, filter, firstValueFrom, map, merge, scan } from 'rxjs'; @@ -71,6 +72,7 @@ export function fetchAll( const query = getAppState().query; const prevQuery = dataSubjects.documents$.getValue().query; const isEsqlQuery = isOfAggregateQueryType(query); + if (reset) { sendResetMsg(dataSubjects, initialFetchStatus); } @@ -85,21 +87,19 @@ export function fetchAll( }); } - const shouldFetchEsql = isEsqlQuery && !!query; - // Mark all subjects as loading sendLoadingMsg(dataSubjects.main$); sendLoadingMsg(dataSubjects.documents$); // histogram for data view mode will send `loading` for totalHits$ - if (shouldFetchEsql) { + if (isEsqlQuery) { sendLoadingMsg(dataSubjects.totalHits$, { result: dataSubjects.totalHits$.getValue().result, }); } // Start fetching all required requests - const response = shouldFetchEsql + const response = isEsqlQuery ? fetchEsql( query, dataView, @@ -109,8 +109,9 @@ export function fetchAll( abortController.signal ) : fetchDocuments(searchSource, fetchDeps); - const fetchType = shouldFetchEsql ? 'fetchTextBased' : 'fetchDocuments'; + const fetchType = isEsqlQuery ? 'fetchTextBased' : 'fetchDocuments'; const startTime = window.performance.now(); + // Handle results of the individual queries and forward the results to the corresponding dataSubjects response .then(({ records, esqlQueryColumns, interceptedWarnings, esqlHeaderWarning }) => { @@ -123,7 +124,7 @@ export function fetchAll( }); } - if (shouldFetchEsql) { + if (isEsqlQuery) { dataSubjects.totalHits$.next({ fetchStatus: FetchStatus.COMPLETE, result: records.length, @@ -140,6 +141,7 @@ export function fetchAll( }); } } + /** * The partial state for ES|QL mode is necessary in case the query has changed * In the follow up useEsqlMode hook in this case new columns are added to AppState @@ -201,7 +203,6 @@ export async function fetchMoreDocuments( try { const { getAppState, getInternalState, services, savedSearch } = fetchDeps; const searchSource = savedSearch.searchSource.createChild(); - const dataView = searchSource.getField('index')!; const query = getAppState().query; const isEsqlQuery = isOfAggregateQueryType(query); diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 89d4949ed758f..560f4cb03535e 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -19,7 +19,6 @@ import { getSavedSearchFullPathUrl } from '@kbn/saved-search-plugin/public'; import useObservable from 'react-use/lib/useObservable'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { withSuspense } from '@kbn/shared-ux-utility'; -import { isOfAggregateQueryType } from '@kbn/es-query'; import { getInitialESQLQuery } from '@kbn/esql-utils'; import { ESQL_TYPE } from '@kbn/data-view-utils'; import { useUrl } from './hooks/use_url'; @@ -115,7 +114,7 @@ export function DiscoverMainRoute({ return true; // bypass NoData screen } - if (isOfAggregateQueryType(stateContainer.appState.getState().query)) { + if (isDataSourceType(stateContainer.appState.getState().dataSource, DataSourceType.Esql)) { return true; } diff --git a/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts b/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts index c65e8f29478c2..a907b1e796c87 100644 --- a/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts +++ b/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts @@ -5,6 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import { isEqual } from 'lodash'; import { isOfAggregateQueryType, getAggregateQueryMode } from '@kbn/es-query'; import { useCallback, useEffect, useRef } from 'react'; diff --git a/src/plugins/discover/public/application/main/state_management/utils/cleanup_url_state.ts b/src/plugins/discover/public/application/main/state_management/utils/cleanup_url_state.ts index ebf5f8dc90bd3..3486d5aa6a0a9 100644 --- a/src/plugins/discover/public/application/main/state_management/utils/cleanup_url_state.ts +++ b/src/plugins/discover/public/application/main/state_management/utils/cleanup_url_state.ts @@ -5,7 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { isOfAggregateQueryType } from '@kbn/es-query'; + +import { isOfAggregateQueryType, isOfQueryType } from '@kbn/es-query'; import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { DiscoverAppState, AppStateUrl } from '../discover_app_state_container'; import { migrateLegacyQuery } from '../../../../utils/migrate_legacy_query'; @@ -20,11 +21,7 @@ export function cleanupUrlState( appStateFromUrl: AppStateUrl, uiSettings: IUiSettingsClient ): DiscoverAppState { - if ( - appStateFromUrl.query && - !isOfAggregateQueryType(appStateFromUrl.query) && - !appStateFromUrl.query.language - ) { + if (isOfQueryType(appStateFromUrl.query) && !appStateFromUrl.query.language) { appStateFromUrl.query = migrateLegacyQuery(appStateFromUrl.query); } @@ -51,9 +48,11 @@ export function cleanupUrlState( delete appStateFromUrl.rowsPerPage; } + const isEsqlQuery = isOfAggregateQueryType(appStateFromUrl.query); + if ( appStateFromUrl.sampleSize && - (isOfAggregateQueryType(appStateFromUrl.query) || // not supported yet for ES|QL + (isEsqlQuery || // not supported yet for ES|QL !( typeof appStateFromUrl.sampleSize === 'number' && appStateFromUrl.sampleSize > 0 && @@ -67,7 +66,7 @@ export function cleanupUrlState( if (appStateFromUrl.index) { if (!appStateFromUrl.dataSource) { // Convert the provided index to a data source - appStateFromUrl.dataSource = isOfAggregateQueryType(appStateFromUrl.query) + appStateFromUrl.dataSource = isEsqlQuery ? createEsqlDataSource() : createDataViewDataSource({ dataViewId: appStateFromUrl.index }); } diff --git a/src/plugins/discover/public/application/main/state_management/utils/get_state_defaults.ts b/src/plugins/discover/public/application/main/state_management/utils/get_state_defaults.ts index add0f1b99b6f2..7e44adf64dbc1 100644 --- a/src/plugins/discover/public/application/main/state_management/utils/get_state_defaults.ts +++ b/src/plugins/discover/public/application/main/state_management/utils/get_state_defaults.ts @@ -21,11 +21,7 @@ import { DiscoverAppState } from '../discover_app_state_container'; import { DiscoverServices } from '../../../../build_services'; import { getDefaultSort, getSortArray } from '../../../../utils/sorting'; import { getValidViewMode } from '../../utils/get_valid_view_mode'; -import { - createDataViewDataSource, - createEsqlDataSource, - DiscoverDataSource, -} from '../../../../../common/data_sources'; +import { createDataViewDataSource, createEsqlDataSource } from '../../../../../common/data_sources'; function getDefaultColumns(savedSearch: SavedSearch, uiSettings: IUiSettingsClient) { if (savedSearch.columns && savedSearch.columns.length > 0) { @@ -55,7 +51,7 @@ export function getStateDefaults({ const sort = getSortArray(savedSearch.sort ?? [], dataView!, isEsqlQuery); const columns = getDefaultColumns(savedSearch, uiSettings); const chartHidden = getChartHidden(storage, 'discover'); - const dataSource: DiscoverDataSource | undefined = isEsqlQuery + const dataSource = isEsqlQuery ? createEsqlDataSource() : dataView?.id ? createDataViewDataSource({ dataViewId: dataView.id })