diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx index effa9f99d6c4b..5535990f72f4c 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx @@ -45,6 +45,17 @@ async function mountComponent(fetchStatus: FetchStatus, hits: EsHitRecord[]) { stateContainer.appState.update({ dataSource: createDataViewDataSource({ dataViewId: dataViewMock.id! }), }); + stateContainer.internalState.transitions.setDataRequestParams({ + timeRangeRelative: { + from: '2020-05-14T11:05:13.590', + to: '2020-05-14T11:20:13.590', + }, + timeRangeAbsolute: { + from: '2020-05-14T11:05:13.590', + to: '2020-05-14T11:20:13.590', + }, + }); + stateContainer.dataState.data$.documents$ = documents$; const props = { diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index 18996a7cdf9ca..ee00abbe5659d 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -46,7 +46,6 @@ import useObservable from 'react-use/lib/useObservable'; import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common'; import { useQuerySubscriber } from '@kbn/unified-field-list'; -import { map } from 'rxjs'; import { DiscoverGrid } from '../../../../components/discover_grid'; import { getDefaultRowsPerPage } from '../../../../../common/constants'; import { useInternalStateSelector } from '../../state_management/discover_internal_state_container'; @@ -112,6 +111,7 @@ function DiscoverDocumentsComponent({ const documents$ = stateContainer.dataState.data$.documents$; const savedSearch = useSavedSearchInitial(); const { dataViews, capabilities, uiSettings, uiActions, ebtManager, fieldsMetadata } = services; + const requestParams = useInternalStateSelector((state) => state.dataRequestParams); const [ dataSource, query, @@ -269,20 +269,14 @@ function DiscoverDocumentsComponent({ : undefined, [documentState.esqlQueryColumns] ); - const { filters } = useQuerySubscriber({ data: services.data }); - const timeRange = useObservable( - services.timefilter.getTimeUpdate$().pipe(map(() => services.timefilter.getTime())), - services.timefilter.getTime() - ); - const cellActionsMetadata = useAdditionalCellActions({ dataSource, dataView, query, filters, - timeRange, + timeRange: requestParams.timeRangeAbsolute, }); const renderDocumentView = useCallback( diff --git a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx index 9aec3589c753e..0513a690e8b8a 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx @@ -38,20 +38,30 @@ import { createDataViewDataSource } from '../../../../../common/data_sources'; function getStateContainer(savedSearch?: SavedSearch) { const stateContainer = getDiscoverStateMock({ isTimeBased: true, savedSearch }); const dataView = savedSearch?.searchSource?.getField('index') as DataView; - - stateContainer.appState.update({ + const appState = { dataSource: createDataViewDataSource({ dataViewId: dataView?.id! }), interval: 'auto', hideChart: false, - }); + }; + + stateContainer.appState.update(appState); stateContainer.internalState.transitions.setDataView(dataView); + stateContainer.internalState.transitions.setDataRequestParams({ + timeRangeAbsolute: { + from: '2020-05-14T11:05:13.590', + to: '2020-05-14T11:20:13.590', + }, + timeRangeRelative: { + from: '2020-05-14T11:05:13.590', + to: '2020-05-14T11:20:13.590', + }, + }); return stateContainer; } const mountComponent = async ({ - isEsqlMode = false, storage, savedSearch = savedSearchMockWithTimeField, searchSessionId = '123', @@ -65,16 +75,7 @@ const mountComponent = async ({ const dataView = savedSearch?.searchSource?.getField('index') as DataView; let services = discoverServiceMock; - services.data.query.timefilter.timefilter.getAbsoluteTime = () => { - return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; - }; - services.data.query.timefilter.timefilter.getTime = () => { - return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; - }; - (services.data.query.queryString.getDefaultQuery as jest.Mock).mockReturnValue({ - language: 'kuery', - query: '', - }); + (searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation( jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } })) ); @@ -176,8 +177,6 @@ describe('Discover histogram layout component', () => { const { component } = await mountComponent(); expect(component.find(PanelsToggle).first().prop('isChartAvailable')).toBe(undefined); expect(component.find(PanelsToggle).first().prop('renderedFor')).toBe('histogram'); - expect(component.find(PanelsToggle).last().prop('isChartAvailable')).toBe(true); - expect(component.find(PanelsToggle).last().prop('renderedFor')).toBe('tabs'); }); }); }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx index 20afaf22373d5..65838d5fb44d5 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx @@ -75,6 +75,18 @@ export const DiscoverHistogramLayout = ({ if (!searchSessionId && !isEsqlMode) { return null; } + if (hideChart) { + return ( + <> + + + ); + } return ( { it('should include PanelsToggle when chart is available', async () => { const component = await mountComponent({ isChartAvailable: true }); - expect(component.find(PanelsToggle).prop('isChartAvailable')).toBe(true); - expect(component.find(PanelsToggle).prop('renderedFor')).toBe('tabs'); + expect(component.find(PanelsToggle).prop('renderedFor')).toBe('root'); expect(component.find(EuiHorizontalRule).exists()).toBe(true); }); it('should include PanelsToggle when chart is available and hidden', async () => { const component = await mountComponent({ isChartAvailable: true, hideChart: true }); - expect(component.find(PanelsToggle).prop('isChartAvailable')).toBe(true); - expect(component.find(PanelsToggle).prop('renderedFor')).toBe('tabs'); + expect(component.find(PanelsToggle).prop('renderedFor')).toBe('root'); expect(component.find(EuiHorizontalRule).exists()).toBe(false); }); it('should include PanelsToggle when chart is not available', async () => { const component = await mountComponent({ isChartAvailable: false }); - expect(component.find(PanelsToggle).prop('isChartAvailable')).toBe(false); - expect(component.find(PanelsToggle).prop('renderedFor')).toBe('tabs'); + expect(component.find(PanelsToggle).prop('renderedFor')).toBe('root'); expect(component.find(EuiHorizontalRule).exists()).toBe(false); }); }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx index 78801581418a4..f61a3cdffd644 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx @@ -106,23 +106,11 @@ export const DiscoverMainContent = ({ setDiscoverViewMode={setDiscoverViewMode} patternCount={patternCount} dataView={dataView} - prepend={ - React.isValidElement(panelsToggle) - ? React.cloneElement(panelsToggle, { renderedFor: 'tabs', isChartAvailable }) - : undefined - } + prepend={panelsToggle} /> ); }, - [ - viewMode, - isEsqlMode, - stateContainer, - setDiscoverViewMode, - dataView, - panelsToggle, - isChartAvailable, - ] + [viewMode, isEsqlMode, stateContainer, setDiscoverViewMode, dataView, panelsToggle] ); const viewModeToggle = useMemo(() => renderViewModeToggle(), [renderViewModeToggle]); diff --git a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx index c8f829d442444..863a162c63a93 100644 --- a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx @@ -379,6 +379,23 @@ describe('useDiscoverHistogram', () => { }); expect(hook.result.current.isChartLoading).toBe(true); }); + + it('should use timerange + timeRangeRelative + query given by the internalState container', async () => { + const fetch$ = new Subject(); + const stateContainer = getStateContainer(); + const timeRangeAbs = { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' }; + const timeRangeRel = { from: 'now-15m', to: 'now' }; + stateContainer.internalState.transitions.setDataRequestParams({ + timeRangeAbsolute: timeRangeAbs, + timeRangeRelative: timeRangeRel, + }); + const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + act(() => { + fetch$.next(); + }); + expect(hook.result.current.timeRange).toBe(timeRangeAbs); + expect(hook.result.current.relativeTimeRange).toBe(timeRangeRel); + }); }); describe('refetching', () => { diff --git a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts index 3f2acf0ce933b..b66df7edcd904 100644 --- a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts +++ b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.ts @@ -217,14 +217,9 @@ export const useDiscoverHistogram = ({ * Request params */ const { query, filters } = useQuerySubscriber({ data: services.data }); + const requestParams = useInternalStateSelector((state) => state.dataRequestParams); const customFilters = useInternalStateSelector((state) => state.customFilters); - const timefilter = services.data.query.timefilter.timefilter; - const timeRange = timefilter.getAbsoluteTime(); - const relativeTimeRange = useObservable( - timefilter.getTimeUpdate$().pipe(map(() => timefilter.getTime())), - timefilter.getTime() - ); - + const { timeRangeRelative: relativeTimeRange, timeRangeAbsolute: timeRange } = requestParams; // When in ES|QL mode, update the data view, query, and // columns only when documents are done fetching so the Lens suggestions // don't frequently change, such as when the user modifies the table @@ -272,15 +267,15 @@ export const useDiscoverHistogram = ({ * Data fetching */ - const skipRefetch = useRef(); + const skipRefetch = useRef(); // Skip refetching when showing the chart since Lens will // automatically fetch when the chart is shown useEffect(() => { if (skipRefetch.current === undefined) { - skipRefetch.current = false; - } else { - skipRefetch.current = !hideChart; + skipRefetch.current = 0; + } else if (!hideChart) { + skipRefetch.current = 3; } }, [hideChart]); @@ -309,13 +304,14 @@ export const useDiscoverHistogram = ({ } const subscription = fetchChart$.subscribe((source) => { - if (!skipRefetch.current) { + if (skipRefetch.current === 0) { if (source === 'discover') addLog('Unified Histogram - Discover refetch'); if (source === 'lens') addLog('Unified Histogram - Lens suggestion refetch'); unifiedHistogram.refetch(); + } else { + addLog('Unified Histogram - Skip refetch'); + skipRefetch.current = skipRefetch.current! - 1; } - - skipRefetch.current = false; }); // triggering the initial request for total hits hook @@ -499,8 +495,25 @@ const createTotalHitsObservable = (state$?: Observable) = const createCurrentSuggestionObservable = (state$: Observable) => { return state$.pipe( - map((state) => state.currentSuggestionContext), - distinctUntilChanged(isEqual) + // Emit the previous and current state as a pair + pairwise(), + // Filter out the transition where chartHidden changes from false to true + filter(([prev, curr]) => { + const isTransition = prev.chartHidden && !curr.chartHidden; + if (isTransition) { + // console.log('Filtered out transition from chartHidden: false to true'); + return false; + } + /** + const differences = getDifferences( + prev.currentSuggestionContext.suggestion, + curr.currentSuggestionContext.suggestion + ); + console.log('Differences in currentSuggestionContext:', differences); + **/ + + return !isEqual(prev.currentSuggestionContext, curr.currentSuggestionContext); + }) ); }; diff --git a/src/plugins/discover/public/application/main/data_fetching/fetch_all.test.ts b/src/plugins/discover/public/application/main/data_fetching/fetch_all.test.ts index ce2edba5d231e..0f620dab03654 100644 --- a/src/plugins/discover/public/application/main/data_fetching/fetch_all.test.ts +++ b/src/plugins/discover/public/application/main/data_fetching/fetch_all.test.ts @@ -81,6 +81,7 @@ describe('test fetchAll', () => { rowHeight: false, breakdownField: false, }, + dataRequestParams: {}, }), searchSessionId: '123', initialFetchStatus: FetchStatus.UNINITIALIZED, @@ -273,6 +274,7 @@ describe('test fetchAll', () => { rowHeight: false, breakdownField: false, }, + dataRequestParams: {}, }), }; fetchAll(subjects, false, deps); @@ -396,6 +398,7 @@ describe('test fetchAll', () => { rowHeight: false, breakdownField: false, }, + dataRequestParams: {}, }), }; fetchAll(subjects, false, deps); 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 f8552411c0add..95a02128eba84 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 @@ -7,20 +7,33 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { Adapters } from '@kbn/inspector-plugin/common'; +import { Adapters, RequestAdapter } from '@kbn/inspector-plugin/common'; import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; import { BehaviorSubject, + catchError, combineLatest, distinctUntilChanged, filter, firstValueFrom, + lastValueFrom, + map, + of, race, switchMap, } from 'rxjs'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { isEqual } from 'lodash'; -import { isOfAggregateQueryType } from '@kbn/es-query'; +import { + type AggregateQuery, + type Filter, + isOfAggregateQueryType, + type Query, + type TimeRange, +} from '@kbn/es-query'; +import { DataView, DataViewType } from '@kbn/data-views-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { isRunningResponse } from '@kbn/data-plugin/common'; import type { DiscoverAppState } from '../state_management/discover_app_state_container'; import { updateVolatileSearchSource } from './update_search_source'; import { @@ -97,6 +110,7 @@ export function fetchAll( services, sort: getAppState().sort as SortOrder[], customFilters: getInternalState().customFilters, + inputTimeRange: getInternalState().dataRequestParams.timeRangeAbsolute, }); } @@ -117,8 +131,29 @@ export function fetchAll( data, expressions, profilesManager, + inputTimeRange: getInternalState().dataRequestParams.timeRangeAbsolute, }) : fetchDocuments(searchSource, fetchDeps); + const responseTotalHits = + getAppState().hideChart && query && !isEsqlQuery + ? fetchTotalHitsSearchSource({ + services, + searchSessionId: fetchDeps.searchSessionId, + adapter: inspectorAdapters.requests, + abortSignal: abortController.signal, + dataView, + filters: getInternalState().customFilters, + query, + timeRange: getInternalState().dataRequestParams.timeRangeAbsolute!, + }) + : undefined; + responseTotalHits?.then(({ result, resultStatus }) => { + dataSubjects.totalHits$.next({ + fetchStatus: resultStatus, + result, + }); + }); + const fetchType = isEsqlQuery ? 'fetchTextBased' : 'fetchDocuments'; const startTime = window.performance.now(); @@ -281,3 +316,88 @@ const noResultsFound = (subject: DataMain$) => { ) ); }; + +const fetchTotalHitsSearchSource = async ({ + services: { data }, + abortSignal, + adapter, + dataView, + searchSessionId, + filters: originalFilters, + query, + timeRange, +}: { + services: DiscoverServices; + abortSignal: AbortSignal; + dataView: DataView; + searchSessionId: string | undefined; + adapter: RequestAdapter | undefined; + filters: Filter[]; + query: Query | AggregateQuery; + timeRange: TimeRange; +}) => { + const searchSource = data.search.searchSource.createEmpty(); + + searchSource + .setField('index', dataView) + .setField('query', query) + .setField('size', 0) + .setField('trackTotalHits', true); + + let filters = originalFilters; + + if (dataView.type === DataViewType.ROLLUP) { + // We treat that data view as "normal" even if it was a rollup data view, + // since the rollup endpoint does not support querying individual documents, but we + // can get them from the regular _search API that will be used if the data view + // not a rollup data view. + searchSource.setOverwriteDataViewType(undefined); + } else { + // Set the date range filter fields from timeFilter using the absolute format. + // Search sessions requires that it be converted from a relative range + const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, timeRange); + + if (timeFilter) { + filters = [...filters, timeFilter]; + } + } + + searchSource.setField('filter', filters); + + // Let the consumer inspect the request if they want to track it + const inspector = adapter + ? { + adapter, + title: i18n.translate('discover.inspectorRequestDataTitleTotalHits', { + defaultMessage: 'Total hits', + }), + description: i18n.translate('discove.inspectorRequestDescriptionTotalHits', { + defaultMessage: 'This request queries Elasticsearch to fetch the total hits.', + }), + } + : undefined; + + const fetch$ = searchSource + .fetch$({ + inspector, + sessionId: searchSessionId, + abortSignal, + executionContext: { + description: 'fetch total hits', + }, + disableWarningToasts: true, // TODO: show warnings as a badge next to total hits number + }) + .pipe( + filter((res) => !isRunningResponse(res)), + map((res) => res.rawResponse.hits.total as number), + catchError((error: Error) => of(error)) + ); + + const result = await lastValueFrom(fetch$); + + const resultStatus = result instanceof Error ? FetchStatus.ERROR : FetchStatus.COMPLETE; + + const nrOfHits = result instanceof Error ? undefined : result; + + return { resultStatus, result: nrOfHits }; +}; diff --git a/src/plugins/discover/public/application/main/data_fetching/fetch_esql.test.ts b/src/plugins/discover/public/application/main/data_fetching/fetch_esql.test.ts index f4f2d6e4fa4af..4fcac182a7864 100644 --- a/src/plugins/discover/public/application/main/data_fetching/fetch_esql.test.ts +++ b/src/plugins/discover/public/application/main/data_fetching/fetch_esql.test.ts @@ -13,13 +13,23 @@ import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { of } from 'rxjs'; import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; import { discoverServiceMock } from '../../../__mocks__/services'; -import { fetchEsql } from './fetch_esql'; +import { fetchEsql, getTextBasedQueryStateToAstProps } from './fetch_esql'; +import { TimeRange } from '@kbn/es-query'; describe('fetchEsql', () => { beforeEach(() => { jest.clearAllMocks(); }); + const fetchEsqlMockProps = { + query: { esql: 'from *' }, + dataView: dataViewWithTimefieldMock, + inspectorAdapters: { requests: new RequestAdapter() }, + data: discoverServiceMock.data, + expressions: discoverServiceMock.expressions, + profilesManager: discoverServiceMock.profilesManager, + }; + it('resolves with returned records', async () => { const hits = [ { _id: '1', foo: 'bar' }, @@ -46,16 +56,7 @@ describe('fetchEsql', () => { discoverServiceMock.profilesManager, 'resolveDocumentProfile' ); - expect( - await fetchEsql({ - query: { esql: 'from *' }, - dataView: dataViewWithTimefieldMock, - inspectorAdapters: { requests: new RequestAdapter() }, - data: discoverServiceMock.data, - expressions: discoverServiceMock.expressions, - profilesManager: discoverServiceMock.profilesManager, - }) - ).toEqual({ + expect(await fetchEsql(fetchEsqlMockProps)).toEqual({ records, esqlQueryColumns: ['_id', 'foo'], esqlHeaderWarning: undefined, @@ -64,4 +65,24 @@ describe('fetchEsql', () => { expect(resolveDocumentProfileSpy).toHaveBeenCalledWith({ record: records[0] }); expect(resolveDocumentProfileSpy).toHaveBeenCalledWith({ record: records[1] }); }); + + it('should use inputTimeRange if provided', () => { + const inputTimeRange: TimeRange = { from: 'now-15m', to: 'now' }; + const result = getTextBasedQueryStateToAstProps({ ...fetchEsqlMockProps, inputTimeRange }); + expect(result.time).toEqual(inputTimeRange); + }); + + it('should use absolute time from data if inputTimeRange is not provided', () => { + const absoluteTimeRange: TimeRange = { + from: '2021-08-31T22:00:00.000Z', + to: '2021-09-01T22:00:00.000Z', + }; + jest + .spyOn(discoverServiceMock.data.query.timefilter.timefilter, 'getAbsoluteTime') + .mockReturnValue(absoluteTimeRange); + + const result = getTextBasedQueryStateToAstProps(fetchEsqlMockProps); + + expect(result.time).toEqual(absoluteTimeRange); + }); }); diff --git a/src/plugins/discover/public/application/main/data_fetching/fetch_esql.ts b/src/plugins/discover/public/application/main/data_fetching/fetch_esql.ts index d7cad10b177f6..254dc96a31c44 100644 --- a/src/plugins/discover/public/application/main/data_fetching/fetch_esql.ts +++ b/src/plugins/discover/public/application/main/data_fetching/fetch_esql.ts @@ -51,20 +51,16 @@ export function fetchEsql({ expressions: ExpressionsStart; profilesManager: ProfilesManager; }): Promise { - const timeRange = inputTimeRange ?? data.query.timefilter.timefilter.getTime(); - return textBasedQueryStateToAstWithValidation({ - filters, + const props = getTextBasedQueryStateToAstProps({ query, - time: timeRange, - timeFieldName: dataView.timeFieldName, inputQuery, - titleForInspector: i18n.translate('discover.inspectorEsqlRequestTitle', { - defaultMessage: 'Table', - }), - descriptionForInspector: i18n.translate('discover.inspectorEsqlRequestDescription', { - defaultMessage: 'This request queries Elasticsearch to fetch results for the table.', - }), - }) + filters, + inputTimeRange, + dataView, + inspectorAdapters, + data, + }); + return textBasedQueryStateToAstWithValidation(props) .then((ast) => { if (ast) { const contract = expressions.execute(ast, null, { @@ -118,3 +114,34 @@ export function fetchEsql({ throw new Error(err.message); }); } +export function getTextBasedQueryStateToAstProps({ + query, + inputQuery, + filters, + inputTimeRange, + dataView, + data, +}: { + query: Query | AggregateQuery; + inputQuery?: Query; + filters?: Filter[]; + inputTimeRange?: TimeRange; + dataView: DataView; + inspectorAdapters: Adapters; + data: DataPublicPluginStart; +}) { + const timeRange = inputTimeRange ?? data.query.timefilter.timefilter.getAbsoluteTime(); + return { + filters, + query, + time: timeRange, + timeFieldName: dataView.timeFieldName, + inputQuery, + titleForInspector: i18n.translate('discover.inspectorEsqlRequestTitle', { + defaultMessage: 'Table', + }), + descriptionForInspector: i18n.translate('discover.inspectorEsqlRequestDescription', { + defaultMessage: 'This request queries Elasticsearch to fetch results for the table.', + }), + }; +} diff --git a/src/plugins/discover/public/application/main/data_fetching/update_search_source.ts b/src/plugins/discover/public/application/main/data_fetching/update_search_source.ts index ad79e93ec37e4..05ba512d0e716 100644 --- a/src/plugins/discover/public/application/main/data_fetching/update_search_source.ts +++ b/src/plugins/discover/public/application/main/data_fetching/update_search_source.ts @@ -7,9 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { ISearchSource } from '@kbn/data-plugin/public'; -import { DataViewType, DataView } from '@kbn/data-views-plugin/public'; -import { Filter } from '@kbn/es-query'; +import type { ISearchSource } from '@kbn/data-plugin/public'; +import { DataViewType, type DataView } from '@kbn/data-views-plugin/public'; +import type { Filter, TimeRange } from '@kbn/es-query'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; import { SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils'; import { DiscoverServices } from '../../../build_services'; @@ -25,11 +25,13 @@ export function updateVolatileSearchSource( services, sort, customFilters, + inputTimeRange, }: { dataView: DataView; services: DiscoverServices; sort?: SortOrder[]; customFilters: Filter[]; + inputTimeRange?: TimeRange; } ) { const { uiSettings, data } = services; @@ -48,7 +50,7 @@ export function updateVolatileSearchSource( if (dataView.type !== DataViewType.ROLLUP) { // Set the date range filter fields from timeFilter using the absolute format. Search sessions requires that it be converted from a relative range - const timeFilter = data.query.timefilter.timefilter.createFilter(dataView); + const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, inputTimeRange); filters = timeFilter ? [...filters, timeFilter] : filters; } diff --git a/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts b/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts index 59220d7def3c1..29960268a8144 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts @@ -13,7 +13,6 @@ import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { AggregateQuery, isOfAggregateQueryType, Query } from '@kbn/es-query'; -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { DataView } from '@kbn/data-views-plugin/common'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import type { SearchResponseWarning } from '@kbn/search-response-warnings'; @@ -66,14 +65,6 @@ export interface DataTotalHitsMsg extends DataMsg { result?: number; } -export interface DataChartsMessage extends DataMsg { - response?: SearchResponse; -} - -export interface DataAvailableFieldsMsg extends DataMsg { - fields?: string[]; -} - export interface DiscoverDataStateContainer { /** * Implicitly starting fetching data from ES @@ -106,7 +97,7 @@ export interface DiscoverDataStateContainer { /** * resetting all data observable to initial state */ - reset: () => void; + reset: (status?: FetchStatus) => void; /** * cancels the running queries @@ -254,6 +245,11 @@ export function getDataStateContainer({ return; } + internalStateContainer.transitions.setDataRequestParams({ + timeRangeAbsolute: timefilter.getAbsoluteTime(), + timeRangeRelative: timefilter.getTime(), + }); + await profilesManager.resolveDataSourceProfile({ dataSource: appStateContainer.getState().dataSource, dataView: getSavedSearch().searchSource.getField('index'), @@ -366,8 +362,9 @@ export function getDataStateContainer({ return refetch$; }; - const reset = () => { - sendResetMsg(dataSubjects, getInitialFetchStatus()); + const reset = (status?: FetchStatus) => { + const fetchStatus = status || getInitialFetchStatus(); + sendResetMsg(dataSubjects, fetchStatus); }; const cancel = () => { diff --git a/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts b/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts index 8aad8eb91738b..74815ad816daf 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts @@ -14,10 +14,15 @@ import { ReduxLikeStateContainer, } from '@kbn/kibana-utils-plugin/common'; import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/common'; -import type { Filter } from '@kbn/es-query'; +import type { Filter, TimeRange } from '@kbn/es-query'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram-plugin/public'; +interface InternalStateDataRequestParams { + timeRangeAbsolute?: TimeRange; + timeRangeRelative?: TimeRange; +} + export interface InternalState { dataView: DataView | undefined; isDataViewLoading: boolean; @@ -33,6 +38,7 @@ export interface InternalState { rowHeight: boolean; breakdownField: boolean; }; + dataRequestParams: InternalStateDataRequestParams; } export interface InternalStateTransitions { @@ -65,6 +71,9 @@ export interface InternalStateTransitions { ) => ( resetDefaultProfileState: Omit ) => InternalState; + setDataRequestParams: ( + state: InternalState + ) => (params: InternalStateDataRequestParams) => InternalState; } export type DiscoverInternalStateContainer = ReduxLikeStateContainer< @@ -91,6 +100,7 @@ export function getInternalStateContainer() { rowHeight: false, breakdownField: false, }, + dataRequestParams: {}, }, { setDataView: (prevState: InternalState) => (nextDataView: DataView) => ({ @@ -162,6 +172,11 @@ export function getInternalStateContainer() { overriddenVisContextAfterInvalidation: undefined, expandedDoc: undefined, }), + setDataRequestParams: + (prevState: InternalState) => (params: InternalStateDataRequestParams) => ({ + ...prevState, + dataRequestParams: params, + }), setResetDefaultProfileState: (prevState: InternalState) => (resetDefaultProfileState: Omit) => ({ diff --git a/src/plugins/discover/public/application/main/state_management/utils/load_saved_search.ts b/src/plugins/discover/public/application/main/state_management/utils/load_saved_search.ts index 295bfe01c5d7a..c875533208215 100644 --- a/src/plugins/discover/public/application/main/state_management/utils/load_saved_search.ts +++ b/src/plugins/discover/public/application/main/state_management/utils/load_saved_search.ts @@ -10,6 +10,7 @@ import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { cloneDeep, isEqual } from 'lodash'; import { isOfAggregateQueryType } from '@kbn/es-query'; +import { FetchStatus } from '../../../types'; import { getEsqlDataView } from './get_esql_data_view'; import { loadAndResolveDataView } from './resolve_data_view'; import { DiscoverInternalStateContainer } from '../discover_internal_state_container'; @@ -162,7 +163,7 @@ function updateBySavedSearch(savedSearch: SavedSearch, deps: LoadSavedSearchDeps } // Finally notify dataStateContainer, data.query and filterManager about new derived state - dataStateContainer.reset(); + dataStateContainer.reset(FetchStatus.SETUP); // set data service filters const filters = savedSearch.searchSource.getField('filter'); if (Array.isArray(filters) && filters.length) { diff --git a/src/plugins/discover/public/application/main/utils/get_result_state.ts b/src/plugins/discover/public/application/main/utils/get_result_state.ts index 070ba65b0e902..9c16b7155f0e7 100644 --- a/src/plugins/discover/public/application/main/utils/get_result_state.ts +++ b/src/plugins/discover/public/application/main/utils/get_result_state.ts @@ -21,7 +21,7 @@ export const resultStatuses = { * Determines what is displayed in Discover main view (loading view, data view, empty data view, ...) */ export function getResultState(fetchStatus: FetchStatus, foundDocuments: boolean = false) { - if (fetchStatus === FetchStatus.UNINITIALIZED) { + if (fetchStatus === FetchStatus.UNINITIALIZED || fetchStatus === FetchStatus.SETUP) { return resultStatuses.UNINITIALIZED; } if (fetchStatus === FetchStatus.ERROR) return resultStatuses.NO_RESULTS; diff --git a/src/plugins/discover/public/application/types.ts b/src/plugins/discover/public/application/types.ts index 9d25158750f10..433f775797f5a 100644 --- a/src/plugins/discover/public/application/types.ts +++ b/src/plugins/discover/public/application/types.ts @@ -12,6 +12,7 @@ import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { SearchResponseWarning } from '@kbn/search-response-warnings'; export enum FetchStatus { + SETUP = 'setup', UNINITIALIZED = 'uninitialized', LOADING = 'loading', LOADING_MORE = 'loading_more', diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts index 1399074816903..dca6e37dfdd5f 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts @@ -129,7 +129,7 @@ const fetchTotalHits = async ({ onTotalHitsChange?.(response.resultStatus, response.result); }; -const fetchTotalHitsSearchSource = async ({ +export const fetchTotalHitsSearchSource = async ({ services: { data }, abortController, dataView, diff --git a/test/functional/apps/discover/group3/_request_counts.ts b/test/functional/apps/discover/group3/_request_counts.ts index ecf84ede5a714..267d5f891a740 100644 --- a/test/functional/apps/discover/group3/_request_counts.ts +++ b/test/functional/apps/discover/group3/_request_counts.ts @@ -158,7 +158,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Creating saved search'); await expectSearches( type, - type === 'esql' ? actualExpectedRequests + 2 : actualExpectedRequests, + type === 'esql' ? actualExpectedRequests + 1 : actualExpectedRequests, async () => { await discover.saveSearch(savedSearch); } @@ -173,7 +173,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Clearing saved search'); await expectSearches( type, - type === 'esql' ? actualExpectedRequests + 2 : actualExpectedRequests, + type === 'esql' ? actualExpectedRequests + 1 : actualExpectedRequests, async () => { await testSubjects.click('discoverNewButton'); await waitForLoadingToFinish(); @@ -182,7 +182,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Loading saved search'); await expectSearches( type, - type === 'esql' ? actualExpectedRequests + 2 : actualExpectedRequests, + type === 'esql' ? actualExpectedRequests + 1 : actualExpectedRequests, async () => { await discover.loadSavedSearch(savedSearch); } @@ -285,7 +285,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await expectSearches(type, 1, async () => { await discover.toggleChartVisibility(); }); - await expectSearches(type, 3, async () => { + await expectSearches(type, 2, async () => { await discover.toggleChartVisibility(); }); });