From 0755f6d7738731d51aff8d2819e8581950a0a6bc Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Mon, 23 Jan 2023 08:30:51 +0100 Subject: [PATCH] [Discover] Add container for data state (migrated from useSavedSearch hook) (#148614) This PR migrates the hook responsible for data fetching in Discover main (`use_saved_search.ts`) to DataStateContainer. --- .../discover/public/__mocks__/services.ts | 4 + .../field_stats_table/field_stats_table.tsx | 23 +- .../layout/__stories__/get_layout_props.ts | 2 +- .../layout/discover_documents.test.tsx | 6 +- .../components/layout/discover_documents.tsx | 5 +- .../layout/discover_histogram_layout.test.tsx | 8 +- .../layout/discover_histogram_layout.tsx | 8 +- .../layout/discover_layout.test.tsx | 11 +- .../components/layout/discover_layout.tsx | 27 +- .../layout/discover_main_content.test.tsx | 7 +- .../layout/discover_main_content.tsx | 9 - .../main/components/layout/types.ts | 10 +- .../layout/use_discover_histogram.test.tsx | 2 +- .../layout/use_discover_histogram.ts | 2 +- .../discover_field_details.test.tsx | 2 +- .../discover_field_details.tsx | 2 +- .../sidebar/discover_field.test.tsx | 2 +- .../components/sidebar/discover_field.tsx | 2 +- .../sidebar/discover_sidebar.test.tsx | 2 +- .../components/sidebar/discover_sidebar.tsx | 2 +- .../discover_sidebar_responsive.test.tsx | 6 +- .../sidebar/discover_sidebar_responsive.tsx | 6 +- .../application/main/discover_main_app.tsx | 6 - .../application/main/hooks/use_data_state.ts | 2 +- .../main/hooks/use_discover_state.ts | 61 ++-- .../main/hooks/use_saved_search.test.tsx | 171 ----------- .../main/hooks/use_saved_search.ts | 272 ------------------ .../hooks/use_saved_search_messages.test.ts | 2 +- .../main/hooks/use_saved_search_messages.ts | 3 +- .../main/hooks/use_search_session.test.ts | 5 +- .../main/hooks/use_search_session.ts | 19 +- .../use_test_based_query_language.test.ts | 2 +- .../hooks/use_text_based_query_language.ts | 2 +- .../discover_data_state_container.test.ts | 91 ++++++ .../services/discover_data_state_container.ts | 252 ++++++++++++++++ .../main/services/discover_state.test.ts | 1 + .../main/services/discover_state.ts | 33 ++- .../application/main/utils/fetch_all.test.ts | 3 +- .../application/main/utils/fetch_all.ts | 2 +- .../main/utils/get_fetch_observable.ts | 17 +- .../main/utils/get_fetch_observeable.test.ts | 4 +- .../main/utils/get_raw_record_type.test.ts | 2 +- .../main/utils/get_raw_record_type.ts | 2 +- .../embeddable/saved_search_embeddable.tsx | 2 +- .../public/utils/get_sharing_data.test.ts | 11 +- .../discover/public/utils/get_sharing_data.ts | 12 +- 46 files changed, 492 insertions(+), 633 deletions(-) delete mode 100644 src/plugins/discover/public/application/main/hooks/use_saved_search.test.tsx delete mode 100644 src/plugins/discover/public/application/main/hooks/use_saved_search.ts create mode 100644 src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts create mode 100644 src/plugins/discover/public/application/main/services/discover_data_state_container.ts diff --git a/src/plugins/discover/public/__mocks__/services.ts b/src/plugins/discover/public/__mocks__/services.ts index d21bc4fc115b3..648ba828489bf 100644 --- a/src/plugins/discover/public/__mocks__/services.ts +++ b/src/plugins/discover/public/__mocks__/services.ts @@ -43,6 +43,10 @@ export function createDiscoverServicesMock(): DiscoverServices { dataPlugin.query.timefilter.timefilter.getTime = jest.fn(() => { return { from: 'now-15m', to: 'now' }; }); + dataPlugin.query.timefilter.timefilter.getRefreshInterval = jest.fn(() => { + return { pause: true, value: 1000 }; + }); + dataPlugin.query.timefilter.timefilter.calculateBounds = jest.fn(calculateBounds); dataPlugin.query.getState = jest.fn(() => ({ query: { query: '', language: 'lucene' }, diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx index 88479e0ee10b0..b01a05f932e13 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx +++ b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx @@ -23,7 +23,6 @@ import { css } from '@emotion/react'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { FIELD_STATISTICS_LOADED } from './constants'; import type { DiscoverStateContainer } from '../../services/discover_state'; -import { AvailableFields$, DataRefetch$, DataTotalHits$ } from '../../hooks/use_saved_search'; export interface RandomSamplingOption { mode: 'random_sampling'; seed: string; @@ -106,15 +105,11 @@ export interface FieldStatisticsTableProps { * @param eventName */ trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; - savedSearchRefetch$?: DataRefetch$; - availableFields$?: AvailableFields$; searchSessionId?: string; - savedSearchDataTotalHits$?: DataTotalHits$; } export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { const { - availableFields$, dataView, savedSearch, query, @@ -123,10 +118,9 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { stateContainer, onAddFilter, trackUiMetric, - savedSearchRefetch$, searchSessionId, - savedSearchDataTotalHits$, } = props; + const totalHits$ = stateContainer?.dataState.data$.totalHits$; const services = useDiscoverServices(); const [embeddable, setEmbeddable] = useState< | ErrorEmbeddable @@ -141,13 +135,14 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { ); useEffect(() => { + const availableFields$ = stateContainer?.dataState.data$.availableFields$; const sub = embeddable?.getOutput$().subscribe((output: DataVisualizerGridEmbeddableOutput) => { if (output.showDistributions !== undefined && stateContainer) { stateContainer.setAppState({ hideAggregatedPreview: !output.showDistributions }); } }); - const refetch = savedSearchRefetch$?.subscribe(() => { + const refetch = stateContainer?.dataState.refetch$.subscribe(() => { if (embeddable && !isErrorEmbeddable(embeddable)) { embeddable.updateInput({ lastReloadRequestTime: Date.now() }); } @@ -164,7 +159,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { refetch?.unsubscribe(); fields?.unsubscribe(); }; - }, [embeddable, stateContainer, savedSearchRefetch$, availableFields$]); + }, [embeddable, stateContainer]); useEffect(() => { if (embeddable && !isErrorEmbeddable(embeddable)) { @@ -177,10 +172,8 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { visibleFieldNames: columns, onAddFilter, sessionId: searchSessionId, - fieldsToFetch: availableFields$?.getValue().fields, - totalDocuments: savedSearchDataTotalHits$ - ? savedSearchDataTotalHits$.getValue()?.result - : undefined, + fieldsToFetch: stateContainer?.dataState.data$.availableFields$?.getValue().fields, + totalDocuments: totalHits$ ? totalHits$.getValue()?.result : undefined, samplingOption: { mode: 'normal_sampling', shardSize: 5000, @@ -198,8 +191,8 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { filters, onAddFilter, searchSessionId, - availableFields$, - savedSearchDataTotalHits$, + totalHits$, + stateContainer, ]); useEffect(() => { diff --git a/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts b/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts index f5b64ebf85adc..3d7e72b32bb12 100644 --- a/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts +++ b/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts @@ -18,7 +18,7 @@ import { DataMain$, DataTotalHits$, RecordRawType, -} from '../../../hooks/use_saved_search'; +} from '../../../services/discover_data_state_container'; import { buildDataTableRecordList } from '../../../../../utils/build_data_record'; import { esHits } from '../../../../../__mocks__/es_hits'; import { SavedSearch } from '../../../../..'; 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 c6af7846a4d23..d73c1163c8a0f 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 @@ -13,7 +13,7 @@ import { setHeaderActionMenuMounter } from '../../../../kibana_services'; import { esHits } from '../../../../__mocks__/es_hits'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { DiscoverStateContainer } from '../../services/discover_state'; -import { DataDocuments$ } from '../../hooks/use_saved_search'; +import { DataDocuments$ } from '../../services/discover_data_state_container'; import { discoverServiceMock } from '../../../../__mocks__/services'; import { FetchStatus } from '../../../types'; import { DiscoverDocuments, onResize } from './discover_documents'; @@ -39,14 +39,14 @@ function mountComponent(fetchStatus: FetchStatus, hits: EsHitRecord[]) { }) as DataDocuments$; const stateContainer = getDiscoverStateMock({}); stateContainer.setAppState({ index: dataViewMock.id }); + stateContainer.dataState.data$.documents$ = documents$; const props = { expandedDoc: undefined, dataView: dataViewMock, onAddFilter: jest.fn(), savedSearch: savedSearchMock, - documents$, - searchSource: documents$, + searchSource: savedSearchMock.searchSource, setExpandedDoc: jest.fn(), state: { columns: [] }, stateContainer, 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 fbded519b956a..647a6561b7cf0 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 @@ -29,7 +29,7 @@ import { HIDE_ANNOUNCEMENTS, } from '../../../../../common'; import { useColumns } from '../../../../hooks/use_data_grid_columns'; -import { DataDocuments$, RecordRawType } from '../../hooks/use_saved_search'; +import { RecordRawType } from '../../services/discover_data_state_container'; import { DiscoverStateContainer } from '../../services/discover_state'; import { useDataState } from '../../hooks/use_data_state'; import { DocTableInfinite } from '../../../../components/doc_table/doc_table_infinite'; @@ -59,7 +59,6 @@ export const onResize = ( }; function DiscoverDocumentsComponent({ - documents$, expandedDoc, dataView, onAddFilter, @@ -68,7 +67,6 @@ function DiscoverDocumentsComponent({ stateContainer, onFieldEdited, }: { - documents$: DataDocuments$; expandedDoc?: DataTableRecord; dataView: DataView; navigateTo: (url: string) => void; @@ -79,6 +77,7 @@ function DiscoverDocumentsComponent({ onFieldEdited?: () => void; }) { const services = useDiscoverServices(); + const documents$ = stateContainer.dataState.data$.documents$; const { dataViews, capabilities, uiSettings } = services; const [query, sort, rowHeight, rowsPerPage, grid, columns, index] = useAppStateSelector( (state) => { 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 d99340da89857..765e8edfcdbaf 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 @@ -7,7 +7,7 @@ */ import React from 'react'; -import { Subject, BehaviorSubject, of } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { esHits } from '../../../../__mocks__/es_hits'; import { dataViewMock } from '../../../../__mocks__/data_view'; @@ -18,7 +18,7 @@ import { DataMain$, DataTotalHits$, RecordRawType, -} from '../../hooks/use_saved_search'; +} from '../../services/discover_data_state_container'; import { discoverServiceMock } from '../../../../__mocks__/services'; import { FetchStatus } from '../../../types'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; @@ -117,6 +117,7 @@ const mountComponent = ({ session.getSession$.mockReturnValue(new BehaviorSubject('123')); const stateContainer = getStateContainer(); + stateContainer.dataState.data$ = savedSearchData$; const props: DiscoverHistogramLayoutProps = { isPlainRecord, @@ -124,9 +125,6 @@ const mountComponent = ({ navigateTo: jest.fn(), setExpandedDoc: jest.fn(), savedSearch, - savedSearchData$, - savedSearchFetch$: new Subject(), - savedSearchRefetch$: new Subject(), stateContainer, onFieldEdited: jest.fn(), columns: [], 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 aa4027c22aedc..28a6d6c051787 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 @@ -15,7 +15,6 @@ import type { DiscoverSearchSessionManager } from '../../services/discover_searc import type { InspectorAdapters } from '../../hooks/use_inspector'; import { type DiscoverMainContentProps, DiscoverMainContent } from './discover_main_content'; import { ResetSearchButton } from './reset_search_button'; -import type { DataFetch$ } from '../../hooks/use_saved_search'; export interface DiscoverHistogramLayoutProps extends DiscoverMainContentProps { resetSavedSearch: () => void; @@ -23,7 +22,6 @@ export interface DiscoverHistogramLayoutProps extends DiscoverMainContentProps { resizeRef: RefObject; inspectorAdapters: InspectorAdapters; searchSessionManager: DiscoverSearchSessionManager; - savedSearchFetch$: DataFetch$; } export const DiscoverHistogramLayout = ({ @@ -31,8 +29,6 @@ export const DiscoverHistogramLayout = ({ dataView, resetSavedSearch, savedSearch, - savedSearchData$, - savedSearchFetch$, stateContainer, isTimeBased, resizeRef, @@ -47,14 +43,14 @@ export const DiscoverHistogramLayout = ({ isPlainRecord, stateContainer, savedSearch, - savedSearchData$, + savedSearchData$: stateContainer.dataState.data$, }; const histogramProps = useDiscoverHistogram({ isTimeBased, inspectorAdapters, searchSessionManager, - savedSearchFetch$, + savedSearchFetch$: stateContainer.dataState.fetch$, ...commonProps, }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx index 2d6998df4898f..13994b401b044 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { Subject, BehaviorSubject, of } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { Query, AggregateQuery } from '@kbn/es-query'; import { setHeaderActionMenuMounter } from '../../../../kibana_services'; @@ -24,12 +24,10 @@ import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_ import { AvailableFields$, DataDocuments$, - DataFetch$, DataMain$, - DataRefetch$, DataTotalHits$, RecordRawType, -} from '../../hooks/use_saved_search'; +} from '../../services/discover_data_state_container'; import { createDiscoverServicesMock } from '../../../../__mocks__/services'; import { FetchStatus } from '../../../types'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; @@ -93,7 +91,7 @@ function mountComponent( result: Number(esHits.length), }) as DataTotalHits$; - const savedSearchData$ = { + stateContainer.dataState.data$ = { main$, documents$, totalHits$, @@ -115,9 +113,6 @@ function mountComponent( onUpdateQuery: jest.fn(), resetSavedSearch: jest.fn(), savedSearch: savedSearchMock, - savedSearchData$, - savedSearchFetch$: new Subject() as DataFetch$, - savedSearchRefetch$: new Subject() as DataRefetch$, searchSource: searchSourceMock, state: { columns: [], query, hideChart: false, interval: 'auto' }, stateContainer, 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 f93bb7b9ff8ea..8f1dcc4ba8dfb 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 @@ -38,7 +38,7 @@ import { DiscoverTopNav } from '../top_nav/discover_topnav'; import { DocViewFilterFn } from '../../../../services/doc_views/doc_views_types'; import { getResultState } from '../../utils/get_result_state'; import { DiscoverUninitialized } from '../uninitialized/uninitialized'; -import { DataMainMsg, RecordRawType } from '../../hooks/use_saved_search'; +import { DataMainMsg, RecordRawType } from '../../services/discover_data_state_container'; import { useColumns } from '../../../../hooks/use_data_grid_columns'; import { FetchStatus } from '../../../types'; import { useDataState } from '../../hooks/use_data_state'; @@ -62,10 +62,7 @@ export function DiscoverLayout({ onChangeDataView, onUpdateQuery, setExpandedDoc, - savedSearchFetch$, - savedSearchRefetch$, resetSavedSearch, - savedSearchData$, savedSearch, searchSource, stateContainer, @@ -86,7 +83,7 @@ export function DiscoverLayout({ spaces, inspector, } = useDiscoverServices(); - const { main$ } = savedSearchData$; + const { main$ } = stateContainer.dataState.data$; const [query, savedQuery, filters, columns, sort] = useAppStateSelector((state) => [ state.query, state.savedQuery, @@ -166,8 +163,8 @@ export function DiscoverLayout({ if (!dataView.isPersisted()) { await updateAdHocDataViewId(dataView); } - savedSearchRefetch$.next('reset'); - }, [dataView, savedSearchRefetch$, updateAdHocDataViewId]); + stateContainer.dataState.refetch$.next('reset'); + }, [dataView, stateContainer, updateAdHocDataViewId]); const onDisableFilters = useCallback(() => { const disabledFilters = filterManager @@ -224,7 +221,11 @@ export function DiscoverLayout({ } if (resultState === 'uninitialized') { - return savedSearchRefetch$.next(undefined)} />; + return ( + stateContainer.dataState.refetch$.next(undefined)} + /> + ); } return ( @@ -237,9 +238,6 @@ export function DiscoverLayout({ expandedDoc={expandedDoc} setExpandedDoc={setExpandedDoc} savedSearch={savedSearch} - savedSearchData$={savedSearchData$} - savedSearchFetch$={savedSearchFetch$} - savedSearchRefetch$={savedSearchRefetch$} stateContainer={stateContainer} isTimeBased={isTimeBased} columns={currentColumns} @@ -271,9 +269,6 @@ export function DiscoverLayout({ resetSavedSearch, resultState, savedSearch, - savedSearchData$, - savedSearchFetch$, - savedSearchRefetch$, searchSessionManager, setExpandedDoc, stateContainer, @@ -328,7 +323,7 @@ export function DiscoverLayout({ diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx index a98d3cd85bbb4..054fc36cdbc3e 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { Subject, BehaviorSubject, of } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { esHits } from '../../../../__mocks__/es_hits'; import { dataViewMock } from '../../../../__mocks__/data_view'; @@ -18,7 +18,7 @@ import { DataMain$, DataTotalHits$, RecordRawType, -} from '../../hooks/use_saved_search'; +} from '../../services/discover_data_state_container'; import { createDiscoverServicesMock } from '../../../../__mocks__/services'; import { FetchStatus } from '../../../types'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; @@ -89,6 +89,7 @@ const mountComponent = ({ availableFields$, }; const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + stateContainer.dataState.data$ = savedSearchData$; stateContainer.setAppState({ interval: 'auto', hideChart, @@ -101,8 +102,6 @@ const mountComponent = ({ navigateTo: jest.fn(), setExpandedDoc: jest.fn(), savedSearch, - savedSearchData$, - savedSearchRefetch$: new Subject(), stateContainer, onFieldEdited: jest.fn(), columns: [], 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 f91de3b2a02d3..c6deff28f62ed 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 @@ -16,7 +16,6 @@ import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { DataTableRecord } from '../../../../types'; import { DocumentViewModeToggle } from '../../../../components/view_mode_toggle'; import { DocViewFilterFn } from '../../../../services/doc_views/doc_views_types'; -import { DataRefetch$, SavedSearchData } from '../../hooks/use_saved_search'; import { DiscoverStateContainer } from '../../services/discover_state'; import { FieldStatisticsTab } from '../field_stats_table'; import { DiscoverDocuments } from './discover_documents'; @@ -27,8 +26,6 @@ export interface DiscoverMainContentProps { savedSearch: SavedSearch; isPlainRecord: boolean; navigateTo: (url: string) => void; - savedSearchData$: SavedSearchData; - savedSearchRefetch$: DataRefetch$; stateContainer: DiscoverStateContainer; expandedDoc?: DataTableRecord; setExpandedDoc: (doc?: DataTableRecord) => void; @@ -42,8 +39,6 @@ export const DiscoverMainContent = ({ dataView, isPlainRecord, navigateTo, - savedSearchData$, - savedSearchRefetch$, expandedDoc, setExpandedDoc, viewMode, @@ -85,7 +80,6 @@ export const DiscoverMainContent = ({ )} {viewMode === VIEW_MODE.DOCUMENT_LEVEL ? ( ) : ( )} diff --git a/src/plugins/discover/public/application/main/components/layout/types.ts b/src/plugins/discover/public/application/main/components/layout/types.ts index 34b8650ffc153..0d9b0c37e3009 100644 --- a/src/plugins/discover/public/application/main/components/layout/types.ts +++ b/src/plugins/discover/public/application/main/components/layout/types.ts @@ -9,10 +9,9 @@ import type { Query, TimeRange, AggregateQuery } from '@kbn/es-query'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { ISearchSource } from '@kbn/data-plugin/public'; -import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import type { DataTableRecord } from '../../../../types'; -import type { DiscoverStateContainer } from '../../services/discover_state'; -import type { DataFetch$, DataRefetch$, SavedSearchData } from '../../hooks/use_saved_search'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { DataTableRecord } from '../../../../types'; +import { DiscoverStateContainer } from '../../services/discover_state'; import type { DiscoverSearchSessionManager } from '../../services/discover_search_session'; import type { InspectorAdapters } from '../../hooks/use_inspector'; @@ -28,9 +27,6 @@ export interface DiscoverLayoutProps { expandedDoc?: DataTableRecord; setExpandedDoc: (doc?: DataTableRecord) => void; savedSearch: SavedSearch; - savedSearchData$: SavedSearchData; - savedSearchFetch$: DataFetch$; - savedSearchRefetch$: DataRefetch$; searchSource: ISearchSource; stateContainer: DiscoverStateContainer; persistDataView: (dataView: DataView) => Promise; 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 89059db91e7f5..b075e613b6a55 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 @@ -18,7 +18,7 @@ import { DataMain$, DataTotalHits$, RecordRawType, -} from '../../hooks/use_saved_search'; +} from '../../services/discover_data_state_container'; import type { DiscoverStateContainer } from '../../services/discover_state'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; 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 d380244fe875b..ad676b1a4247d 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 @@ -23,7 +23,7 @@ import { useAppStateSelector } from '../../services/discover_app_state_container import { getUiActions } from '../../../../kibana_services'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { useDataState } from '../../hooks/use_data_state'; -import type { DataFetch$, SavedSearchData } from '../../hooks/use_saved_search'; +import type { DataFetch$, SavedSearchData } from '../../services/discover_data_state_container'; import type { DiscoverStateContainer } from '../../services/discover_state'; import { FetchStatus } from '../../../types'; import type { DiscoverSearchSessionManager } from '../../services/discover_search_session'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.test.tsx index 535459c880988..e31445344b82b 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.test.tsx @@ -15,7 +15,7 @@ import { DataViewField } from '@kbn/data-views-plugin/public'; import { stubDataView, stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { BehaviorSubject } from 'rxjs'; import { FetchStatus } from '../../../../types'; -import { DataDocuments$ } from '../../../hooks/use_saved_search'; +import { DataDocuments$ } from '../../../services/discover_data_state_container'; import { getDataTableRecords } from '../../../../../__fixtures__/real_hits'; describe('discover sidebar field details', function () { diff --git a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.tsx b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.tsx index d1f32ae8df8fb..0a3d1b5c58072 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/deprecated_stats/discover_field_details.tsx @@ -14,8 +14,8 @@ import { DataViewField, DataView } from '@kbn/data-views-plugin/public'; import { DiscoverFieldBucket } from './discover_field_bucket'; import { Bucket } from './types'; import { getDetails, isValidFieldDetails } from './get_details'; -import { DataDocuments$ } from '../../../hooks/use_saved_search'; import { FetchStatus } from '../../../../types'; +import { DataDocuments$ } from '../../../services/discover_data_state_container'; interface DiscoverFieldDetailsProps { /** diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx index c25a6c5010e7e..88a5660db3017 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx @@ -19,7 +19,7 @@ import { stubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { DiscoverAppStateProvider } from '../../services/discover_app_state_container'; import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; import { FetchStatus } from '../../../types'; -import { DataDocuments$ } from '../../hooks/use_saved_search'; +import { DataDocuments$ } from '../../services/discover_data_state_container'; import { getDataTableRecords } from '../../../../__fixtures__/real_hits'; import * as DetailsUtil from './deprecated_stats/get_details'; import { createDiscoverServicesMock } from '../../../../__mocks__/services'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx index 89b143cf46cd3..3c4567bdc66dc 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx @@ -36,7 +36,7 @@ import { getFieldTypeName } from '../../../../utils/get_field_type_name'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { SHOW_LEGACY_FIELD_TOP_VALUES, PLUGIN_ID } from '../../../../../common'; import { getUiActions } from '../../../../kibana_services'; -import { type DataDocuments$ } from '../../hooks/use_saved_search'; +import { type DataDocuments$ } from '../../services/discover_data_state_container'; const FieldInfoIcon: React.FC = memo(() => ( (data$: BehaviorSubject) { const [fetchState, setFetchState] = useState(data$.getValue()); diff --git a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts index a8ff93ef94547..f5b3949e103a9 100644 --- a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts +++ b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts @@ -12,21 +12,15 @@ import { isOfAggregateQueryType } from '@kbn/es-query'; import { type DataView, DataViewType } from '@kbn/data-views-plugin/public'; import { SavedSearch, getSavedSearch } from '@kbn/saved-search-plugin/public'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; +import { useSearchSession } from './use_search_session'; +import { FetchStatus } from '../../types'; import { useTextBasedQueryLanguage } from './use_text_based_query_language'; import { useUrlTracking } from './use_url_tracking'; import { getDiscoverStateContainer } from '../services/discover_state'; import { getStateDefaults } from '../utils/get_state_defaults'; import { DiscoverServices } from '../../../build_services'; import { loadDataView, resolveDataView } from '../utils/resolve_data_view'; -import { useSavedSearch as useSavedSearchData } from './use_saved_search'; -import { - MODIFY_COLUMNS_ON_SWITCH, - SEARCH_FIELDS_FROM_SOURCE, - SEARCH_ON_PAGE_LOAD_SETTING, - SORT_DEFAULT_ORDER_SETTING, -} from '../../../../common'; -import { useSearchSession } from './use_search_session'; -import { FetchStatus } from '../../types'; +import { MODIFY_COLUMNS_ON_SWITCH, SORT_DEFAULT_ORDER_SETTING } from '../../../../common'; import { getDataViewAppState } from '../utils/get_switch_data_view_app_state'; import { DataTableRecord } from '../../../types'; import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; @@ -45,8 +39,6 @@ export function useDiscoverState({ }) { const { uiSettings, data, filterManager, dataViews, toastNotifications, trackUiMetric } = services; - const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); - const { timefilter } = data.query.timefilter; const dataView = savedSearch.searchSource.getField('index')!; @@ -71,25 +63,14 @@ export function useDiscoverState({ const { setUrlTracking } = useUrlTracking(savedSearch, dataView); - const { appState, replaceUrlAppState } = stateContainer; + const { appState, replaceUrlAppState, searchSessionManager } = stateContainer; const [state, setState] = useState(appState.getState()); /** * Search session logic */ - const searchSessionManager = useSearchSession({ services, history, stateContainer, savedSearch }); - - const initialFetchStatus: FetchStatus = useMemo(() => { - // A saved search is created on every page load, so we check the ID to see if we're loading a - // previously saved search or if it is just transient - const shouldSearchOnPageLoad = - uiSettings.get(SEARCH_ON_PAGE_LOAD_SETTING) || - savedSearch.id !== undefined || - timefilter.getRefreshInterval().pause === false || - searchSessionManager.hasSearchSessionIdInURL(); - return shouldSearchOnPageLoad ? FetchStatus.LOADING : FetchStatus.UNINITIALIZED; - }, [uiSettings, savedSearch.id, searchSessionManager, timefilter]); + useSearchSession({ services, stateContainer, savedSearch }); /** * Adhoc data views functionality @@ -121,15 +102,8 @@ export function useDiscoverState({ /** * Data fetching logic */ - const { data$, fetch$, refetch$, reset, inspectorAdapters } = useSavedSearchData({ - initialFetchStatus, - searchSessionManager, - savedSearch, - searchSource, - services, - stateContainer, - useNewFieldsApi, - }); + const { data$, refetch$, reset, inspectorAdapters, initialFetchStatus } = + stateContainer.dataState; /** * State changes (data view, columns), when a text base query result is returned */ @@ -156,6 +130,14 @@ export function useDiscoverState({ return () => stopSync(); }, [stateContainer, filterManager, data, dataView]); + /** + * Data store subscribing to trigger fetching + */ + useEffect(() => { + const stopSync = stateContainer.dataState.subscribe(); + return () => stopSync(); + }, [stateContainer]); + /** * Track state changes that should trigger a fetch */ @@ -199,6 +181,14 @@ export function useDiscoverState({ stateContainer.actions.setDataView(nextDataView); } + if ( + dataViewChanged && + stateContainer.dataState.initialFetchStatus === FetchStatus.UNINITIALIZED + ) { + // stop execution if given data view has changed, and it's not configured to initially start a search in Discover + return; + } + if ( chartDisplayChanged || chartIntervalChanged || @@ -304,7 +294,7 @@ export function useDiscoverState({ * Trigger data fetching on dataView or savedSearch changes */ useEffect(() => { - if (dataView) { + if (dataView && initialFetchStatus === FetchStatus.LOADING) { refetch$.next(undefined); } }, [initialFetchStatus, refetch$, dataView, savedSearch.id]); @@ -320,10 +310,7 @@ export function useDiscoverState({ }, [dataView, stateContainer]); return { - data$, inspectorAdapters, - fetch$, - refetch$, resetSavedSearch, onChangeDataView, onUpdateQuery, diff --git a/src/plugins/discover/public/application/main/hooks/use_saved_search.test.tsx b/src/plugins/discover/public/application/main/hooks/use_saved_search.test.tsx deleted file mode 100644 index c598df55b1d1d..0000000000000 --- a/src/plugins/discover/public/application/main/hooks/use_saved_search.test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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 { Subject } from 'rxjs'; -import { renderHook } from '@testing-library/react-hooks'; -import { createSearchSessionMock } from '../../../__mocks__/search_session'; -import { discoverServiceMock } from '../../../__mocks__/services'; -import { savedSearchMock, savedSearchMockWithSQL } from '../../../__mocks__/saved_search'; -import { RecordRawType, useSavedSearch } from './use_saved_search'; -import { getDiscoverStateContainer } from '../services/discover_state'; -import { useDiscoverState } from './use_discover_state'; -import { FetchStatus } from '../../types'; -import { setUrlTracker } from '../../../kibana_services'; -import { urlTrackerMock } from '../../../__mocks__/url_tracker.mock'; -import React from 'react'; -import { DiscoverMainProvider } from '../services/discover_state_provider'; - -setUrlTracker(urlTrackerMock); -describe('test useSavedSearch', () => { - test('useSavedSearch return is valid', async () => { - const { history, searchSessionManager } = createSearchSessionMock(); - const stateContainer = getDiscoverStateContainer({ - savedSearch: savedSearchMock, - services: discoverServiceMock, - history, - }); - - const { result } = renderHook(() => { - return useSavedSearch({ - initialFetchStatus: FetchStatus.LOADING, - savedSearch: savedSearchMock, - searchSessionManager, - searchSource: savedSearchMock.searchSource.createCopy(), - services: discoverServiceMock, - stateContainer, - useNewFieldsApi: true, - }); - }); - - expect(result.current.refetch$).toBeInstanceOf(Subject); - expect(result.current.data$.main$.getValue().fetchStatus).toBe(FetchStatus.LOADING); - expect(result.current.data$.documents$.getValue().fetchStatus).toBe(FetchStatus.LOADING); - expect(result.current.data$.totalHits$.getValue().fetchStatus).toBe(FetchStatus.LOADING); - }); - test('refetch$ triggers a search', async () => { - const { history, searchSessionManager } = createSearchSessionMock(); - const stateContainer = getDiscoverStateContainer({ - savedSearch: savedSearchMock, - services: discoverServiceMock, - history, - }); - - discoverServiceMock.data.query.timefilter.timefilter.getTime = jest.fn(() => { - return { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' }; - }); - - const { result: resultState } = renderHook( - () => { - return useDiscoverState({ - services: discoverServiceMock, - history, - savedSearch: savedSearchMock, - setExpandedDoc: jest.fn(), - }); - }, - { - wrapper: ({ children }: { children: React.ReactElement }) => ( - {children} - ), - } - ); - - const { result, waitForValueToChange } = renderHook(() => { - return useSavedSearch({ - initialFetchStatus: FetchStatus.LOADING, - savedSearch: savedSearchMock, - searchSessionManager, - searchSource: resultState.current.searchSource, - services: discoverServiceMock, - stateContainer, - useNewFieldsApi: true, - }); - }); - - result.current.refetch$.next(undefined); - - await waitForValueToChange(() => { - return result.current.data$.main$.value.fetchStatus === 'complete'; - }); - - expect(result.current.data$.totalHits$.value.result).toBe(0); - expect(result.current.data$.documents$.value.result).toEqual([]); - }); - - test('reset sets back to initial state', async () => { - const { history, searchSessionManager } = createSearchSessionMock(); - const stateContainer = getDiscoverStateContainer({ - savedSearch: savedSearchMock, - services: discoverServiceMock, - history, - }); - - discoverServiceMock.data.query.timefilter.timefilter.getTime = jest.fn(() => { - return { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' }; - }); - - const { result: resultState } = renderHook( - () => { - return useDiscoverState({ - services: discoverServiceMock, - history, - savedSearch: savedSearchMock, - setExpandedDoc: jest.fn(), - }); - }, - { - wrapper: ({ children }: { children: React.ReactElement }) => ( - {children} - ), - } - ); - - const { result, waitForValueToChange } = renderHook(() => { - return useSavedSearch({ - initialFetchStatus: FetchStatus.LOADING, - savedSearch: savedSearchMock, - searchSessionManager, - searchSource: resultState.current.searchSource, - services: discoverServiceMock, - stateContainer, - useNewFieldsApi: true, - }); - }); - - result.current.refetch$.next(undefined); - - await waitForValueToChange(() => { - return result.current.data$.main$.value.fetchStatus === FetchStatus.COMPLETE; - }); - - result.current.reset(); - expect(result.current.data$.main$.value.fetchStatus).toBe(FetchStatus.LOADING); - }); - - test('useSavedSearch returns plain record raw type', async () => { - const { history, searchSessionManager } = createSearchSessionMock(); - const stateContainer = getDiscoverStateContainer({ - savedSearch: savedSearchMockWithSQL, - services: discoverServiceMock, - history, - }); - - const { result } = renderHook(() => { - return useSavedSearch({ - initialFetchStatus: FetchStatus.LOADING, - savedSearch: savedSearchMockWithSQL, - searchSessionManager, - searchSource: savedSearchMockWithSQL.searchSource.createCopy(), - services: discoverServiceMock, - stateContainer, - useNewFieldsApi: true, - }); - }); - - expect(result.current.data$.main$.getValue().recordRawType).toBe(RecordRawType.PLAIN); - }); -}); diff --git a/src/plugins/discover/public/application/main/hooks/use_saved_search.ts b/src/plugins/discover/public/application/main/hooks/use_saved_search.ts deleted file mode 100644 index 1b1a917740d43..0000000000000 --- a/src/plugins/discover/public/application/main/hooks/use_saved_search.ts +++ /dev/null @@ -1,272 +0,0 @@ -/* - * 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 { useCallback, useEffect, useMemo, useRef } from 'react'; -import { BehaviorSubject, filter, map, Observable, share, Subject, tap } from 'rxjs'; -import type { AutoRefreshDoneFn } from '@kbn/data-plugin/public'; -import { ISearchSource } from '@kbn/data-plugin/public'; -import { RequestAdapter } from '@kbn/inspector-plugin/public'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { AggregateQuery, Query } from '@kbn/es-query'; -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import { getRawRecordType } from '../utils/get_raw_record_type'; -import { DiscoverServices } from '../../../build_services'; -import { DiscoverSearchSessionManager } from '../services/discover_search_session'; -import { DiscoverStateContainer } from '../services/discover_state'; -import { validateTimeRange } from '../utils/validate_time_range'; -import { useSingleton } from './use_singleton'; -import { FetchStatus } from '../../types'; -import { fetchAll } from '../utils/fetch_all'; -import { useBehaviorSubject } from './use_behavior_subject'; -import { sendResetMsg } from './use_saved_search_messages'; -import { getFetch$ } from '../utils/get_fetch_observable'; -import type { DataTableRecord } from '../../../types'; -import type { InspectorAdapters } from './use_inspector'; - -export interface SavedSearchData { - main$: DataMain$; - documents$: DataDocuments$; - totalHits$: DataTotalHits$; - availableFields$: AvailableFields$; -} - -export type DataMain$ = BehaviorSubject; -export type DataDocuments$ = BehaviorSubject; -export type DataTotalHits$ = BehaviorSubject; -export type AvailableFields$ = BehaviorSubject; -export type DataFetch$ = Observable<{ - reset: boolean; - searchSessionId: string; -}>; -export type DataRefetch$ = Subject; - -export interface UseSavedSearch { - refetch$: DataRefetch$; - data$: SavedSearchData; - reset: () => void; - inspectorAdapters: InspectorAdapters; -} - -export enum RecordRawType { - /** - * Documents returned Elasticsearch, nested structure - */ - DOCUMENT = 'document', - /** - * Data returned e.g. SQL queries, flat structure - * */ - PLAIN = 'plain', -} - -export type DataRefetchMsg = 'reset' | undefined; - -export interface DataMsg { - fetchStatus: FetchStatus; - error?: Error; - recordRawType?: RecordRawType; - query?: AggregateQuery | Query | undefined; -} - -export interface DataMainMsg extends DataMsg { - foundDocuments?: boolean; -} - -export interface DataDocumentsMsg extends DataMsg { - result?: DataTableRecord[]; -} - -export interface DataTotalHitsMsg extends DataMsg { - result?: number; -} - -export interface DataChartsMessage extends DataMsg { - response?: SearchResponse; -} - -export interface DataAvailableFieldsMsg extends DataMsg { - fields?: string[]; -} - -/** - * This hook return 2 observables, refetch$ allows to trigger data fetching, data$ to subscribe - * to the data fetching - */ -export const useSavedSearch = ({ - initialFetchStatus, - savedSearch, - searchSessionManager, - searchSource, - services, - stateContainer, - useNewFieldsApi, -}: { - initialFetchStatus: FetchStatus; - savedSearch: SavedSearch; - searchSessionManager: DiscoverSearchSessionManager; - searchSource: ISearchSource; - services: DiscoverServices; - stateContainer: DiscoverStateContainer; - useNewFieldsApi: boolean; -}) => { - const { data, filterManager } = services; - const timefilter = data.query.timefilter.timefilter; - const { query } = stateContainer.appState.getState(); - - const recordRawType = useMemo(() => getRawRecordType(query), [query]); - - const inspectorAdapters = useMemo(() => ({ requests: new RequestAdapter() }), []); - - /** - * The observables the UI (aka React component) subscribes to get notified about - * the changes in the data fetching process (high level: fetching started, data was received) - */ - const initialState = { fetchStatus: initialFetchStatus, recordRawType }; - const main$: DataMain$ = useBehaviorSubject(initialState) as DataMain$; - const documents$: DataDocuments$ = useBehaviorSubject(initialState) as DataDocuments$; - const totalHits$: DataTotalHits$ = useBehaviorSubject(initialState) as DataTotalHits$; - const availableFields$: AvailableFields$ = useBehaviorSubject(initialState) as AvailableFields$; - - const dataSubjects = useMemo(() => { - return { - main$, - documents$, - totalHits$, - availableFields$, - }; - }, [main$, documents$, totalHits$, availableFields$]); - - /** - * The observable to trigger data fetching in UI - * By refetch$.next('reset') rows and fieldcounts are reset to allow e.g. editing of runtime fields - * to be processed correctly - */ - const refetch$ = useSingleton(() => new Subject()); - - /** - * Values that shouldn't trigger re-rendering when changed - */ - const refs = useRef<{ - autoRefreshDone?: AutoRefreshDoneFn; - }>({}); - - /** - * handler emitted by `timefilter.getAutoRefreshFetch$()` - * to notify when data completed loading and to start a new autorefresh loop - */ - const setAutoRefreshDone = useCallback((fn: AutoRefreshDoneFn | undefined) => { - refs.current.autoRefreshDone = fn; - }, []); - - /** - * Observable that allows listening for when fetches are triggered - */ - const fetch$ = useMemo( - () => - getFetch$({ - setAutoRefreshDone, - data, - main$, - refetch$, - searchSessionManager, - searchSource, - initialFetchStatus, - }).pipe( - filter(() => validateTimeRange(timefilter.getTime(), services.toastNotifications)), - tap(() => inspectorAdapters.requests.reset()), - map((val) => ({ - reset: val === 'reset', - searchSessionId: searchSessionManager.getNextSearchSessionId(), - })), - share() - ), - [ - data, - initialFetchStatus, - inspectorAdapters.requests, - main$, - refetch$, - searchSessionManager, - searchSource, - services.toastNotifications, - setAutoRefreshDone, - timefilter, - ] - ); - - /** - * This part takes care of triggering the data fetching by creating and subscribing - * to an observable of various possible changes in state - */ - useEffect(() => { - let abortController: AbortController; - - const subscription = fetch$.subscribe(async ({ reset, searchSessionId }) => { - abortController?.abort(); - abortController = new AbortController(); - - const autoRefreshDone = refs.current.autoRefreshDone; - - await fetchAll(dataSubjects, searchSource, reset, { - abortController, - appStateContainer: stateContainer.appState, - data, - initialFetchStatus, - inspectorAdapters, - savedSearch, - searchSessionId, - services, - useNewFieldsApi, - }); - - // If the autoRefreshCallback is still the same as when we started i.e. there was no newer call - // replacing this current one, call it to make sure we tell that the auto refresh is done - // and a new one can be scheduled. - if (autoRefreshDone === refs.current.autoRefreshDone) { - // if this function was set and is executed, another refresh fetch can be triggered - refs.current.autoRefreshDone?.(); - refs.current.autoRefreshDone = undefined; - } - }); - - return () => { - abortController?.abort(); - subscription.unsubscribe(); - }; - }, [ - data, - data.query.queryString, - dataSubjects, - fetch$, - filterManager, - initialFetchStatus, - inspectorAdapters, - main$, - refetch$, - savedSearch, - searchSessionManager, - searchSessionManager.newSearchSessionIdFromURL$, - searchSource, - services, - services.toastNotifications, - stateContainer.appState, - timefilter, - useNewFieldsApi, - ]); - - const reset = useCallback( - () => sendResetMsg(dataSubjects, initialFetchStatus), - [dataSubjects, initialFetchStatus] - ); - - return { - fetch$, - refetch$, - data$: dataSubjects, - reset, - inspectorAdapters, - }; -}; diff --git a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.test.ts b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.test.ts index 5973d679b6b1c..4f22bf84147f1 100644 --- a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.test.ts @@ -16,7 +16,7 @@ import { } from './use_saved_search_messages'; import { FetchStatus } from '../../types'; import { BehaviorSubject } from 'rxjs'; -import { DataMainMsg, RecordRawType } from './use_saved_search'; +import { DataMainMsg, RecordRawType } from '../services/discover_data_state_container'; import { filter } from 'rxjs/operators'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; diff --git a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts index ab121f76e15a0..cd69acf4fe21d 100644 --- a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts +++ b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts @@ -15,8 +15,7 @@ import type { DataMsg, DataTotalHits$, SavedSearchData, -} from './use_saved_search'; - +} from '../services/discover_data_state_container'; /** * Sends COMPLETE message to the main$ observable with the information * that no documents have been found, allowing Discover to show a no diff --git a/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts b/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts index 931d652d031c5..4f483a3e01c31 100644 --- a/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts @@ -25,14 +25,13 @@ describe('test useSearchSession', () => { const nextId = 'id'; discoverServiceMock.data.search.session.start = jest.fn(() => nextId); - const { result } = renderHook(() => { + renderHook(() => { return useSearchSession({ services: discoverServiceMock, - history, stateContainer, savedSearch: savedSearchMock, }); }); - expect(result.current.getNextSearchSessionId()).toBe('id'); + expect(stateContainer.searchSessionManager.getNextSearchSessionId()).toBe('id'); }); }); diff --git a/src/plugins/discover/public/application/main/hooks/use_search_session.ts b/src/plugins/discover/public/application/main/hooks/use_search_session.ts index 5592993b90d55..8dadbd9015f1e 100644 --- a/src/plugins/discover/public/application/main/hooks/use_search_session.ts +++ b/src/plugins/discover/public/application/main/hooks/use_search_session.ts @@ -5,11 +5,9 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { useMemo, useEffect } from 'react'; -import { History } from 'history'; +import { useEffect } from 'react'; import { noSearchSessionStorageCapabilityMessage } from '@kbn/data-plugin/public'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { DiscoverSearchSessionManager } from '../services/discover_search_session'; import { createSearchSessionRestorationDataProvider, DiscoverStateContainer, @@ -18,27 +16,14 @@ import { DiscoverServices } from '../../../build_services'; export function useSearchSession({ services, - history, stateContainer, savedSearch, }: { services: DiscoverServices; stateContainer: DiscoverStateContainer; - history: History; savedSearch: SavedSearch; }) { const { data, capabilities } = services; - /** - * Search session logic - */ - const searchSessionManager = useMemo( - () => - new DiscoverSearchSessionManager({ - history, - session: data.search.session, - }), - [data.search.session, history] - ); useEffect(() => { data.search.session.enableStorage( @@ -58,6 +43,4 @@ export function useSearchSession({ } ); }, [capabilities.discover.storeSearchSession, data, savedSearch, stateContainer.appState]); - - return searchSessionManager; } diff --git a/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts b/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts index db94bd6638568..0f1b6488f3681 100644 --- a/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts @@ -13,7 +13,7 @@ import { discoverServiceMock } from '../../../__mocks__/services'; import { useTextBasedQueryLanguage } from './use_text_based_query_language'; import { BehaviorSubject } from 'rxjs'; import { FetchStatus } from '../../types'; -import { DataDocuments$, RecordRawType } from './use_saved_search'; +import { DataDocuments$, RecordRawType } from '../services/discover_data_state_container'; import { DataTableRecord } from '../../../types'; import { AggregateQuery, Query } from '@kbn/es-query'; import { dataViewMock } from '../../../__mocks__/data_view'; diff --git a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts index 220a3f1702faa..b47c7145904bf 100644 --- a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts +++ b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts @@ -16,7 +16,7 @@ import { useCallback, useEffect, useRef } from 'react'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; import type { DiscoverStateContainer } from '../services/discover_state'; -import type { DataDocuments$ } from './use_saved_search'; +import type { DataDocuments$ } from '../services/discover_data_state_container'; import { FetchStatus } from '../../types'; const MAX_NUM_OF_COLUMNS = 50; diff --git a/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts b/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts new file mode 100644 index 0000000000000..fcdce1b76a1a0 --- /dev/null +++ b/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts @@ -0,0 +1,91 @@ +/* + * 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 { Subject } from 'rxjs'; +import { waitFor } from '@testing-library/react'; +import { discoverServiceMock } from '../../../__mocks__/services'; +import { savedSearchMockWithSQL } from '../../../__mocks__/saved_search'; +import { getDiscoverStateContainer } from './discover_state'; +import { FetchStatus } from '../../types'; +import { setUrlTracker } from '../../../kibana_services'; +import { urlTrackerMock } from '../../../__mocks__/url_tracker.mock'; +import { RecordRawType } from './discover_data_state_container'; +import { createBrowserHistory } from 'history'; +import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock'; + +setUrlTracker(urlTrackerMock); +describe('test getDataStateContainer', () => { + test('return is valid', async () => { + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + const dataState = stateContainer.dataState; + + expect(dataState.refetch$).toBeInstanceOf(Subject); + expect(dataState.data$.main$.getValue().fetchStatus).toBe(FetchStatus.LOADING); + expect(dataState.data$.documents$.getValue().fetchStatus).toBe(FetchStatus.LOADING); + expect(dataState.data$.totalHits$.getValue().fetchStatus).toBe(FetchStatus.LOADING); + }); + test('refetch$ triggers a search', async () => { + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + + discoverServiceMock.data.query.timefilter.timefilter.getTime = jest.fn(() => { + return { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' }; + }); + const dataState = stateContainer.dataState; + + const unsubscribe = dataState.subscribe(); + + expect(dataState.data$.totalHits$.value.result).toBe(undefined); + expect(dataState.data$.documents$.value.result).toEqual(undefined); + + dataState.refetch$.next(undefined); + await waitFor(() => { + expect(dataState.data$.main$.value.fetchStatus).toBe('complete'); + }); + + expect(dataState.data$.totalHits$.value.result).toBe(0); + expect(dataState.data$.documents$.value.result).toEqual([]); + unsubscribe(); + }); + + test('reset sets back to initial state', async () => { + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + + discoverServiceMock.data.query.timefilter.timefilter.getTime = jest.fn(() => { + return { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' }; + }); + + const dataState = stateContainer.dataState; + const unsubscribe = dataState.subscribe(); + + await waitFor(() => { + expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.LOADING); + }); + + dataState.refetch$.next(undefined); + + await waitFor(() => { + expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE); + }); + dataState.reset(); + await waitFor(() => { + expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.LOADING); + }); + + unsubscribe(); + }); + + test('useSavedSearch returns plain record raw type', async () => { + const history = createBrowserHistory(); + const stateContainer = getDiscoverStateContainer({ + savedSearch: savedSearchMockWithSQL, + services: discoverServiceMock, + history, + }); + + expect(stateContainer.dataState.data$.main$.getValue().recordRawType).toBe(RecordRawType.PLAIN); + }); +}); diff --git a/src/plugins/discover/public/application/main/services/discover_data_state_container.ts b/src/plugins/discover/public/application/main/services/discover_data_state_container.ts new file mode 100644 index 0000000000000..5cadb099c483e --- /dev/null +++ b/src/plugins/discover/public/application/main/services/discover_data_state_container.ts @@ -0,0 +1,252 @@ +/* + * 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 { BehaviorSubject, filter, map, Observable, share, Subject, tap } from 'rxjs'; +import { AutoRefreshDoneFn } from '@kbn/data-plugin/public'; +import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { AggregateQuery, Query } from '@kbn/es-query'; +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import { ReduxLikeStateContainer } from '@kbn/kibana-utils-plugin/common'; +import { getRawRecordType } from '../utils/get_raw_record_type'; +import { AppState } from './discover_app_state_container'; +import { DiscoverServices } from '../../../build_services'; +import { DiscoverSearchSessionManager } from './discover_search_session'; +import { SEARCH_FIELDS_FROM_SOURCE, SEARCH_ON_PAGE_LOAD_SETTING } from '../../../../common'; +import { FetchStatus } from '../../types'; +import { validateTimeRange } from '../utils/validate_time_range'; +import { fetchAll } from '../utils/fetch_all'; +import { sendResetMsg } from '../hooks/use_saved_search_messages'; +import { getFetch$ } from '../utils/get_fetch_observable'; +import { DataTableRecord } from '../../../types'; + +export interface SavedSearchData { + main$: DataMain$; + documents$: DataDocuments$; + totalHits$: DataTotalHits$; + availableFields$: AvailableFields$; +} + +export type DataMain$ = BehaviorSubject; +export type DataDocuments$ = BehaviorSubject; +export type DataTotalHits$ = BehaviorSubject; +export type AvailableFields$ = BehaviorSubject; +export type DataFetch$ = Observable<{ + reset: boolean; + searchSessionId: string; +}>; + +export type DataRefetch$ = Subject; + +export enum RecordRawType { + /** + * Documents returned Elasticsearch, nested structure + */ + DOCUMENT = 'document', + /** + * Data returned e.g. SQL queries, flat structure + * */ + PLAIN = 'plain', +} + +export type DataRefetchMsg = 'reset' | undefined; + +export interface DataMsg { + fetchStatus: FetchStatus; + error?: Error; + recordRawType?: RecordRawType; + query?: AggregateQuery | Query | undefined; +} + +export interface DataMainMsg extends DataMsg { + foundDocuments?: boolean; +} + +export interface DataDocumentsMsg extends DataMsg { + result?: DataTableRecord[]; +} + +export interface DataTotalHitsMsg extends DataMsg { + result?: number; +} + +export interface DataChartsMessage extends DataMsg { + response?: SearchResponse; +} + +export interface DataAvailableFieldsMsg extends DataMsg { + fields?: string[]; +} + +export interface DataStateContainer { + /** + * Implicitly starting fetching data from ES + */ + fetch: () => void; + /** + * Observable emitting when a next fetch is triggered + */ + fetch$: DataFetch$; + /** + * Container of data observables (orchestration, data table, total hits, available fields) + */ + data$: SavedSearchData; + /** + * Observable triggering fetching data from ES + */ + refetch$: DataRefetch$; + /** + * Start subscribing to other observables that trigger data fetches + */ + subscribe: () => () => void; + /** + * resetting all data observable to initial state + */ + reset: () => void; + /** + * Available Inspector Adaptor allowing to get details about recent requests to ES + */ + inspectorAdapters: { requests: RequestAdapter }; + /** + * Initial fetch status + * UNINITIALIZED: data is not fetched initially, without user triggering it + * LOADING: data is fetched initially (when Discover is rendered, or data views are switched) + */ + initialFetchStatus: FetchStatus; +} +/** + * Container responsible for fetching of data in Discover Main + * Either by triggering requests to Elasticsearch directly, or by + * orchestrating unified plugins / components like the histogram + */ +export function getDataStateContainer({ + services, + searchSessionManager, + getAppState, + getSavedSearch, + appStateContainer, +}: { + services: DiscoverServices; + searchSessionManager: DiscoverSearchSessionManager; + getAppState: () => AppState; + getSavedSearch: () => SavedSearch; + appStateContainer: ReduxLikeStateContainer; +}): DataStateContainer { + const { data, uiSettings, toastNotifications } = services; + const { timefilter } = data.query.timefilter; + const inspectorAdapters = { requests: new RequestAdapter() }; + const appState = getAppState(); + const recordRawType = getRawRecordType(appState.query); + /** + * The observable to trigger data fetching in UI + * By refetch$.next('reset') rows and fieldcounts are reset to allow e.g. editing of runtime fields + * to be processed correctly + */ + const refetch$ = new Subject(); + const shouldSearchOnPageLoad = + uiSettings.get(SEARCH_ON_PAGE_LOAD_SETTING) || + getSavedSearch().id !== undefined || + !timefilter.getRefreshInterval().pause || + searchSessionManager.hasSearchSessionIdInURL(); + const initialFetchStatus = shouldSearchOnPageLoad + ? FetchStatus.LOADING + : FetchStatus.UNINITIALIZED; + + /** + * The observables the UI (aka React component) subscribes to get notified about + * the changes in the data fetching process (high level: fetching started, data was received) + */ + const initialState = { fetchStatus: initialFetchStatus, recordRawType }; + const dataSubjects: SavedSearchData = { + main$: new BehaviorSubject(initialState), + documents$: new BehaviorSubject(initialState), + totalHits$: new BehaviorSubject(initialState), + availableFields$: new BehaviorSubject(initialState), + }; + + let autoRefreshDone: AutoRefreshDoneFn | undefined; + /** + * handler emitted by `timefilter.getAutoRefreshFetch$()` + * to notify when data completed loading and to start a new autorefresh loop + */ + const setAutoRefreshDone = (fn: AutoRefreshDoneFn | undefined) => { + autoRefreshDone = fn; + }; + const fetch$ = getFetch$({ + setAutoRefreshDone, + data, + main$: dataSubjects.main$, + refetch$, + searchSource: getSavedSearch().searchSource, + searchSessionManager, + }).pipe( + filter(() => validateTimeRange(timefilter.getTime(), toastNotifications)), + tap(() => inspectorAdapters.requests.reset()), + map((val) => ({ + reset: val === 'reset', + searchSessionId: searchSessionManager.getNextSearchSessionId(), + })), + share() + ); + let abortController: AbortController; + + function subscribe() { + const subscription = fetch$.subscribe(async ({ reset, searchSessionId }) => { + abortController?.abort(); + abortController = new AbortController(); + const prevAutoRefreshDone = autoRefreshDone; + + await fetchAll(dataSubjects, getSavedSearch().searchSource, reset, { + abortController, + data, + initialFetchStatus, + inspectorAdapters, + searchSessionId, + services, + appStateContainer, + savedSearch: getSavedSearch(), + useNewFieldsApi: !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), + }); + + // If the autoRefreshCallback is still the same as when we started i.e. there was no newer call + // replacing this current one, call it to make sure we tell that the auto refresh is done + // and a new one can be scheduled. + if (autoRefreshDone === prevAutoRefreshDone) { + // if this function was set and is executed, another refresh fetch can be triggered + autoRefreshDone?.(); + autoRefreshDone = undefined; + } + }); + + return () => { + abortController?.abort(); + subscription.unsubscribe(); + }; + } + + const fetchQuery = (resetQuery?: boolean) => { + if (resetQuery) { + refetch$.next('reset'); + } else { + refetch$.next(undefined); + } + return refetch$; + }; + + const reset = () => sendResetMsg(dataSubjects, initialFetchStatus); + + return { + fetch: fetchQuery, + fetch$, + data$: dataSubjects, + refetch$, + subscribe, + reset, + inspectorAdapters, + initialFetchStatus, + }; +} diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index 8136ced9f4ff3..f3ed93a3394df 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -157,6 +157,7 @@ describe('Test discover state with legacy migration', () => { describe('createSearchSessionRestorationDataProvider', () => { let mockSavedSearch: SavedSearch = {} as unknown as SavedSearch; + history = createBrowserHistory(); const mockDataPlugin = dataPluginMock.createStartContract(); const searchSessionInfoProvider = createSearchSessionRestorationDataProvider({ data: mockDataPlugin, diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index 52e0d6e72fbf5..7a8493c87c1f9 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -29,6 +29,8 @@ import { } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { DataStateContainer, getDataStateContainer } from './discover_data_state_container'; +import { DiscoverSearchSessionManager } from './discover_search_session'; import { DiscoverAppLocatorParams, DISCOVER_APP_LOCATOR } from '../../../../common'; import { AppState } from './discover_app_state_container'; import { @@ -52,7 +54,7 @@ interface DiscoverStateContainerParams { /** * Browser history */ - history?: History; + history: History; /** * The current savedSearch */ @@ -76,6 +78,14 @@ export interface DiscoverStateContainer { * Internal state that's used at several places in the UI */ internalState: InternalStateContainer; + /** + * Service for handling search sessions + */ + searchSessionManager: DiscoverSearchSessionManager; + /** + * Data fetching related state + **/ + dataState: DataStateContainer; /** * Initialize state with filters and query, start state syncing */ @@ -179,6 +189,14 @@ export function getDiscoverStateContainer({ ...(toasts && withNotifyOnErrors(toasts)), }); + /** + * Search session logic + */ + const searchSessionManager = new DiscoverSearchSessionManager({ + history, + session: services.data.search.session, + }); + const appStateFromUrl = cleanupUrlState(stateStorage.get(APP_STATE_URL_KEY) as AppStateUrl); let initialAppState = handleSourceColumnState( @@ -234,6 +252,17 @@ export function getDiscoverStateContainer({ } }; + const dataStateContainer = getDataStateContainer({ + services, + searchSessionManager, + getAppState: appStateContainer.getState, + getSavedSearch: () => { + // Simulating the behavior of the removed hook to always create a clean searchSource child that + // we then use to add query, filters, etc., will be removed soon. + return { ...savedSearch, searchSource: savedSearch.searchSource.createChild() }; + }, + appStateContainer, + }); const setDataView = (dataView: DataView) => { internalStateContainer.transitions.setDataView(dataView); }; @@ -255,6 +284,8 @@ export function getDiscoverStateContainer({ kbnUrlStateStorage: stateStorage, appState: appStateContainerModified, internalState: internalStateContainer, + dataState: dataStateContainer, + searchSessionManager, startSync: () => { const { start, stop } = syncAppState(); start(); diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.test.ts b/src/plugins/discover/public/application/main/utils/fetch_all.test.ts index b11389c24b054..95b1cd7618b4d 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.test.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.test.ts @@ -21,8 +21,7 @@ import { DataTotalHitsMsg, RecordRawType, SavedSearchData, -} from '../hooks/use_saved_search'; - +} from '../services/discover_data_state_container'; import { fetchDocuments } from './fetch_documents'; import { fetchSql } from './fetch_sql'; import { buildDataTableRecord } from '../../../utils/build_data_record'; diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.ts b/src/plugins/discover/public/application/main/utils/fetch_all.ts index f698d999662ce..c2a3c0856af06 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.ts @@ -23,7 +23,7 @@ import { import { updateSearchSource } from './update_search_source'; import { fetchDocuments } from './fetch_documents'; import { FetchStatus } from '../../types'; -import { DataMsg, RecordRawType, SavedSearchData } from '../hooks/use_saved_search'; +import { DataMsg, RecordRawType, SavedSearchData } from '../services/discover_data_state_container'; import { DiscoverServices } from '../../../build_services'; import { fetchSql } from './fetch_sql'; diff --git a/src/plugins/discover/public/application/main/utils/get_fetch_observable.ts b/src/plugins/discover/public/application/main/utils/get_fetch_observable.ts index 984d0737dfb31..71490f05ac6ce 100644 --- a/src/plugins/discover/public/application/main/utils/get_fetch_observable.ts +++ b/src/plugins/discover/public/application/main/utils/get_fetch_observable.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import { merge } from 'rxjs'; -import { debounceTime, filter, skip, tap } from 'rxjs/operators'; +import { debounceTime, filter, tap } from 'rxjs/operators'; import type { AutoRefreshDoneFn, @@ -14,7 +14,7 @@ import type { ISearchSource, } from '@kbn/data-plugin/public'; import { FetchStatus } from '../../types'; -import { DataMain$, DataRefetch$ } from '../hooks/use_saved_search'; +import { DataMain$, DataRefetch$ } from '../services/discover_data_state_container'; import { DiscoverSearchSessionManager } from '../services/discover_search_session'; /** @@ -26,7 +26,6 @@ export function getFetch$({ main$, refetch$, searchSessionManager, - initialFetchStatus, }: { setAutoRefreshDone: (val: AutoRefreshDoneFn | undefined) => void; data: DataPublicPluginStart; @@ -34,11 +33,10 @@ export function getFetch$({ refetch$: DataRefetch$; searchSessionManager: DiscoverSearchSessionManager; searchSource: ISearchSource; - initialFetchStatus: FetchStatus; }) { const { timefilter } = data.query.timefilter; const { filterManager } = data.query; - let fetch$ = merge( + return merge( refetch$, filterManager.getFetches$(), timefilter.getFetch$(), @@ -60,13 +58,4 @@ export function getFetch$({ data.query.queryString.getUpdates$(), searchSessionManager.newSearchSessionIdFromURL$.pipe(filter((sessionId) => !!sessionId)) ).pipe(debounceTime(100)); - - /** - * Skip initial fetch when discover:searchOnPageLoad is disabled. - */ - if (initialFetchStatus === FetchStatus.UNINITIALIZED) { - fetch$ = fetch$.pipe(skip(1)); - } - - return fetch$; } diff --git a/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts b/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts index 67dc63b421706..10e1b397dc657 100644 --- a/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts @@ -11,7 +11,7 @@ import { getFetch$ } from './get_fetch_observable'; import { FetchStatus } from '../../types'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { createSearchSessionMock } from '../../../__mocks__/search_session'; -import { DataRefetch$ } from '../hooks/use_saved_search'; +import { DataRefetch$ } from '../services/discover_data_state_container'; import { savedSearchMock, savedSearchMockWithTimeField } from '../../../__mocks__/saved_search'; function createDataMock( @@ -63,7 +63,6 @@ describe('getFetchObservable', () => { data: createDataMock(new Subject(), new Subject(), new Subject(), new Subject()), searchSessionManager: searchSessionManagerMock.searchSessionManager, searchSource: savedSearchMock.searchSource, - initialFetchStatus: FetchStatus.LOADING, }); fetch$.subscribe(() => { @@ -95,7 +94,6 @@ describe('getFetchObservable', () => { data: dataMock, searchSessionManager: searchSessionManagerMock.searchSessionManager, searchSource: savedSearchMockWithTimeField.searchSource, - initialFetchStatus: FetchStatus.LOADING, }); const fetchfnMock = jest.fn(); diff --git a/src/plugins/discover/public/application/main/utils/get_raw_record_type.test.ts b/src/plugins/discover/public/application/main/utils/get_raw_record_type.test.ts index 879ece28a527f..146a5a80a125f 100644 --- a/src/plugins/discover/public/application/main/utils/get_raw_record_type.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_raw_record_type.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { RecordRawType } from '../hooks/use_saved_search'; +import { RecordRawType } from '../services/discover_data_state_container'; import { getRawRecordType } from './get_raw_record_type'; describe('getRawRecordType', () => { diff --git a/src/plugins/discover/public/application/main/utils/get_raw_record_type.ts b/src/plugins/discover/public/application/main/utils/get_raw_record_type.ts index 8a2fc81f06a82..356939e4f2dfd 100644 --- a/src/plugins/discover/public/application/main/utils/get_raw_record_type.ts +++ b/src/plugins/discover/public/application/main/utils/get_raw_record_type.ts @@ -12,7 +12,7 @@ import { isOfAggregateQueryType, getAggregateQueryMode, } from '@kbn/es-query'; -import { RecordRawType } from '../hooks/use_saved_search'; +import { RecordRawType } from '../services/discover_data_state_container'; export function getRawRecordType(query?: Query | AggregateQuery) { if (query && isOfAggregateQueryType(query) && getAggregateQueryMode(query) === 'sql') { diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index 551f1d0bac626..69a8f4115a45e 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -37,7 +37,7 @@ import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { METRIC_TYPE } from '@kbn/analytics'; import { VIEW_MODE } from '../../common/constants'; import { getSortForEmbeddable, SortPair } from '../utils/sorting'; -import { RecordRawType } from '../application/main/hooks/use_saved_search'; +import { RecordRawType } from '../application/main/services/discover_data_state_container'; import { buildDataTableRecord } from '../utils/build_data_record'; import { DataTableRecord, EsHitRecord } from '../types'; import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; diff --git a/src/plugins/discover/public/utils/get_sharing_data.test.ts b/src/plugins/discover/public/utils/get_sharing_data.test.ts index 103a40423dc35..d1d636a2d1c83 100644 --- a/src/plugins/discover/public/utils/get_sharing_data.test.ts +++ b/src/plugins/discover/public/utils/get_sharing_data.test.ts @@ -78,6 +78,12 @@ describe('getSharingData', () => { const { getSearchSource } = await getSharingData(searchSourceMock, {}, services); expect(getSearchSource()).toMatchInlineSnapshot(` Object { + "fields": Array [ + Object { + "field": "*", + "include_unmapped": "true", + }, + ], "index": "the-data-view-id", "sort": Array [ Object { @@ -194,10 +200,7 @@ describe('getSharingData', () => { test('fields conditionally do not have prepended timeField', async () => { services.uiSettings = { get: (key: string) => { - if (key === DOC_HIDE_TIME_COLUMN_SETTING) { - return true; - } - return false; + return key === DOC_HIDE_TIME_COLUMN_SETTING; }, } as unknown as IUiSettingsClient; diff --git a/src/plugins/discover/public/utils/get_sharing_data.ts b/src/plugins/discover/public/utils/get_sharing_data.ts index dfbdcc49e90a4..9a32a2431649b 100644 --- a/src/plugins/discover/public/utils/get_sharing_data.ts +++ b/src/plugins/discover/public/utils/get_sharing_data.ts @@ -96,11 +96,13 @@ export async function getSharingData( * Discover does not set fields, since having all fields is needed for the UI. */ const useFieldsApi = !config.get(SEARCH_FIELDS_FROM_SOURCE); - if (useFieldsApi && columns.length) { - searchSource.setField( - 'fields', - columns.map((field) => ({ field, include_unmapped: 'true' })) - ); + if (useFieldsApi) { + searchSource.removeField('fieldsFromSource'); + const fields = columns.length + ? columns.map((field) => ({ field, include_unmapped: 'true' })) + : [{ field: '*', include_unmapped: 'true' }]; + + searchSource.setField('fields', fields); } return searchSource.getSerializedFields(true); },