diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.ts b/src/plugins/discover/public/application/angular/context/api/anchor.ts index f2111d020aade..75d262dbfbbff 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.ts +++ b/src/plugins/discover/public/application/angular/context/api/anchor.ts @@ -16,16 +16,18 @@ import { } from '../../../../../../data/public'; import { EsHitRecord } from './context'; +export type FetchAnchorCallback = ( + indexPatternId: string, + anchorId: string, + sort: EsQuerySortValue[] +) => Promise; + export function fetchAnchorProvider( indexPatterns: IndexPatternsContract, searchSource: ISearchSource, useNewFieldsApi: boolean = false -) { - return async function fetchAnchor( - indexPatternId: string, - anchorId: string, - sort: EsQuerySortValue[] - ): Promise { +): FetchAnchorCallback { + return async function fetchAnchor(indexPatternId, anchorId, sort) { const indexPattern = await indexPatterns.get(indexPatternId); searchSource .setParent(undefined) diff --git a/src/plugins/discover/public/application/angular/context_query_state.ts b/src/plugins/discover/public/application/angular/context_query_state.ts index bf2b435d025b5..488e919804da4 100644 --- a/src/plugins/discover/public/application/angular/context_query_state.ts +++ b/src/plugins/discover/public/application/angular/context_query_state.ts @@ -17,6 +17,15 @@ export interface ContextQueryState { loadingStatus: LoadingStatusState; queryParameters: QueryParameters; rows: ContextRows; + hits: EsHitRecordList; + predecessors: EsHitRecordList; + successors: EsHitRecordList; + anchor: EsHitRecord; + anchorStatus: LoadingState; + predecessorsStatus: LoadingState; + successorsStatus: LoadingState; + predecessorCount: number; + successorCount: number; useNewFieldsApi: boolean; } @@ -42,6 +51,8 @@ export interface LoadingStatusState { successors: LoadingStatusEntry | LoadingStatus; } +export type LoadingState = LoadingStatusEntry | LoadingStatus; + export interface QueryParameters { anchorId: string; columns: string[]; @@ -54,11 +65,11 @@ export interface QueryParameters { tieBreakerField: string; } -interface ContextRows { +export interface ContextRows { all: EsHitRecordList; anchor: EsHitRecord; - // predecessors: EsHitRecordList; - // successors: EsHitRecordList; + predecessors: EsHitRecordList; + successors: EsHitRecordList; } export function getContextQueryDefaults( @@ -75,6 +86,14 @@ export function getContextQueryDefaults( defaultStepSize, tieBreakerField ), + hits: [], + predecessors: [], + successors: [], + anchor: { + fields: [], + sort: [], + _id: '', + }, rows: { all: [], anchor: { @@ -82,10 +101,15 @@ export function getContextQueryDefaults( sort: [], _id: '', }, - // predecessors: [], - // successors: [], + predecessors: [], + successors: [], }, loadingStatus: createInitialLoadingStatusState(), + anchorStatus: LoadingStatus.UNINITIALIZED, + predecessorsStatus: LoadingStatus.UNINITIALIZED, + successorsStatus: LoadingStatus.UNINITIALIZED, + predecessorCount: 5, + successorCount: 5, useNewFieldsApi, }; } diff --git a/src/plugins/discover/public/application/angular/context_state.ts b/src/plugins/discover/public/application/angular/context_state.ts index 9b1a52cdb4e0a..304e83f774e2b 100644 --- a/src/plugins/discover/public/application/angular/context_state.ts +++ b/src/plugins/discover/public/application/angular/context_state.ts @@ -29,18 +29,18 @@ export interface AppState extends ContextQueryState { * Array of filters */ filters?: Filter[]; - /** - * Number of records to be fetched before anchor records (newer records) - */ - predecessorCount?: number; + // /** + // * Number of records to be fetched before anchor records (newer records) + // */ + // predecessorCount?: number; /** * Sorting of the records to be fetched, assumed to be a legacy parameter */ sort: [[string, SortDirection]]; - /** - * Number of records to be fetched after the anchor records (older records) - */ - successorCount: number; + // /** + // * Number of records to be fetched after the anchor records (older records) + // */ + // successorCount: number; query?: Query; } diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx index 95fd080480603..4062edf3909b8 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx @@ -6,12 +6,14 @@ * Side Public License, v 1. */ -import React, { useState, Fragment, useEffect, useRef } from 'react'; +import React, { useState, Fragment, useEffect, useRef, useCallback } from 'react'; import classNames from 'classnames'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import './context_app_legacy.scss'; import { EuiHorizontalRule, EuiText, EuiPageContent, EuiPage, EuiSpacer } from '@elastic/eui'; import { cloneDeep, isEqual } from 'lodash'; +import { FilterManager } from 'src/plugins/data/public'; +import { map } from 'rxjs/operators'; import { DOC_HIDE_TIME_COLUMN_SETTING, DOC_TABLE_LEGACY } from '../../../../common'; import { ContextErrorMessage } from '../context_error_message'; import { @@ -19,7 +21,7 @@ import { DocTableLegacyProps, } from '../../angular/doc_table/create_doc_table_react'; import { IndexPattern } from '../../../../../data/common/index_patterns'; -import { LoadingStatus } from '../../angular/context_query_state'; +import { LoadingState, LoadingStatus } from '../../angular/context_query_state'; import { ActionBar, ActionBarProps } from '../../angular/context/components/action_bar/action_bar'; import { TopNavMenuProps } from '../../../../../navigation/public'; import { DiscoverGrid, DiscoverGridProps } from '../discover_grid/discover_grid'; @@ -31,6 +33,7 @@ import { EsHitRecord, EsHitRecordList } from '../../angular/context/api/context' import { useContextAppState } from './use_context_app_state'; import { useContextAppQuery } from './use_context_app_query'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from '../../angular/context/query_parameters'; +// import { fetchAnchorRow } from './context_app_actions'; export interface ContextAppProps { topNavMenu: React.ComponentType; @@ -62,7 +65,7 @@ const DataGridMemoized = React.memo(DiscoverGrid); const PREDECESSOR_TYPE = 'predecessors'; const SUCCESSOR_TYPE = 'successors'; -function isLoading(status: string) { +function isLoading(status: LoadingState) { return status !== LoadingStatus.LOADED && status !== LoadingStatus.FAILED; } @@ -76,9 +79,9 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { const { indexPattern, indexPatternId, - anchorStatus, - predecessorStatus, - successorStatus, + // anchorStatus, + // predecessorStatus, + // successorStatus, // hits: rows, sorting, anchorId, @@ -87,16 +90,16 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { useNewFieldsApi, } = renderProps; const [expandedDoc, setExpandedDoc] = useState(undefined); - const isAnchorLoaded = anchorStatus === LoadingStatus.LOADED; - const isFailed = anchorStatus === LoadingStatus.FAILED; - const allRowsLoaded = - anchorStatus === LoadingStatus.LOADED && - predecessorStatus === LoadingStatus.LOADED && - successorStatus === LoadingStatus.LOADED; + // const isAnchorLoaded = anchor === LoadingStatus.LOADED; + // const isFailed = anchorStatus === LoadingStatus.FAILED; + // const allRowsLoaded = + // anchorStatus === LoadingStatus.LOADED && + // predecessorStatus === LoadingStatus.LOADED && + // successorStatus === LoadingStatus.LOADED; const isLegacy = config.get(DOC_TABLE_LEGACY); // const anchorId = rows?.find(({ isAnchor }) => isAnchor)?._id; - const { state, stateContainer, setAppState } = useContextAppState({ + const { state, setAppState } = useContextAppState({ indexPattern, indexPatternId, anchorId, @@ -104,16 +107,22 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { }); const prevState = useRef(); - useEffect(() => { - stateContainer.startSync(); + const isAnchorLoaded = state.anchorStatus === LoadingStatus.LOADED; + const isFailed = state.anchorStatus === LoadingStatus.FAILED; + const allRowsLoaded = + state.anchorStatus === LoadingStatus.LOADED && + state.predecessorsStatus === LoadingStatus.LOADED && + state.successorsStatus === LoadingStatus.LOADED; - return () => stateContainer.stopSync(); - }, [stateContainer]); + // useEffect(() => { + // stateContainer.startSync(); - const { context$, fetchAnchorRow, fetchAllRows } = useContextAppQuery({ + // return () => stateContainer.stopSync(); + // }, [stateContainer]); + + const { context$, fetchContextRows, fetchAllRows } = useContextAppQuery({ services, useNewFieldsApi: !!useNewFieldsApi, - state, }); /** @@ -121,30 +130,44 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { */ useEffect(() => { if (!prevState.current) { - fetchAllRows(); + fetchAllRows(state.predecessorCount, state.successorCount, { + indexPatternId, + anchorId, + tieBreakerField: state.queryParameters.tieBreakerField, + sort: state.sort, + }); } else if ( prevState.current.predecessorCount !== state.predecessorCount || prevState.current.successorCount !== state.successorCount || !isEqual(prevState.current.filters, state.filters) ) { - fetchAllRows(); + fetchContextRows(state.predecessorCount, state.successorCount, { + indexPatternId, + anchor: state.anchor, + tieBreakerField: state.queryParameters.tieBreakerField, + sort: state.sort, + }); } prevState.current = cloneDeep(state); - }, [state, fetchAllRows, fetchAnchorRow, indexPatternId]); + }, [state, indexPatternId, anchorId, fetchContextRows, fetchAllRows]); /** * Sync app state with context$ */ useEffect(() => { - context$.subscribe((next) => { - setAppState(next); + context$.subscribe((value) => { + setAppState(value); }); return () => context$.unsubscribe(); }, [context$, setAppState]); - const rows = state.rows; + const rows = [ + ...(state.predecessors || []), + ...(state.anchor ? [state.anchor] : []), + ...(state.successors || []), + ]; const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useDataGridColumns({ capabilities, config, @@ -171,7 +194,9 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { docCount: isPredecessorType ? predecessorCount : successorCount, docCountAvailable: isPredecessorType ? predecessorAvailable : successorAvailable, onChangeCount: isPredecessorType ? onChangePredecessorCount : onChangeSuccessorCount, - isLoading: isPredecessorType ? isLoading(predecessorStatus) : isLoading(successorStatus), + isLoading: isPredecessorType + ? isLoading(state.predecessorsStatus) + : isLoading(state.successorsStatus), type, isDisabled: !isAnchorLoaded, } as ActionBarProps; @@ -181,7 +206,7 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { return { ariaLabelledBy: 'surDocumentsAriaLabel', columns, - rows: rows.all.length !== 0 && rows.all, + rows: rows.length !== 0 && rows, indexPattern, expandedDoc, isLoading: !allRowsLoaded, @@ -230,7 +255,10 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { }; const loadingFeedback = () => { - if (anchorStatus === LoadingStatus.UNINITIALIZED || anchorStatus === LoadingStatus.LOADING) { + if ( + state.anchorStatus === LoadingStatus.UNINITIALIZED || + state.anchorStatus === LoadingStatus.LOADING + ) { return ( @@ -243,7 +271,10 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { return ( {isFailed ? ( - + ) : ( diff --git a/src/plugins/discover/public/application/components/context_app/use_context_app_query.tsx b/src/plugins/discover/public/application/components/context_app/use_context_app_query.tsx index 1463438c87df5..e1bd7fa4908fd 100644 --- a/src/plugins/discover/public/application/components/context_app/use_context_app_query.tsx +++ b/src/plugins/discover/public/application/components/context_app/use_context_app_query.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { fromPairs } from 'lodash'; @@ -20,16 +20,44 @@ import { SurrDocType, } from '../../angular/context/api/context'; import { MarkdownSimple, toMountPoint } from '../../../../../kibana_react/public'; +import { Filter, SortDirection } from '../../../../../data/public'; +import { + ContextRows, + FailureReason, + LoadingState, + LoadingStatus, +} from '../../angular/context_query_state'; export type ContextAppMessage = Partial; +interface ContextDocsMessage { + successors?: EsHitRecordList; + predecessors?: EsHitRecordList; + anchor?: EsHitRecord; + all?: EsHitRecordList; + anchorStatus?: LoadingState; + predecessorsStatus?: LoadingState; + successorsStatus?: LoadingState; +} + +interface FetchAllRowsParams { + anchorId: string; + indexPatternId: string; + tieBreakerField: string; + sort: [[string, SortDirection]]; +} +interface FetchSurrDocsParams { + indexPatternId: string; + anchor: EsHitRecord; + tieBreakerField: string; + sort: [[string, SortDirection]]; +} + export function useContextAppQuery({ services, useNewFieldsApi, - state, }: { services: DiscoverServices; - state: AppState; useNewFieldsApi: boolean; }) { const { data, indexPatterns, toastNotifications, filterManager } = services; @@ -47,68 +75,55 @@ export function useContextAppQuery({ [indexPatterns, useNewFieldsApi] ); - const context$ = useMemo(() => new Subject(), []); + const context$ = useMemo(() => new Subject(), []); - const fetchAnchorRow = useCallback(() => { - const { - queryParameters: { indexPatternId, anchorId, tieBreakerField }, + const fetchAnchorRow = useCallback( + ({ + indexPatternId, + anchorId, + tieBreakerField, sort, - } = state; - - if (!tieBreakerField) { - // reject - } - - // set loading - const [[, sortDir]] = sort; - - return fetchAnchor(indexPatternId, anchorId, [ - fromPairs(sort), - { [tieBreakerField]: sortDir }, - ]).then( - (anchorDocument: EsHitRecord) => { - // set loaded - context$.next({ - rows: { - ...state.rows, - anchor: anchorDocument, - }, - }); - }, - (error: Error) => { - // set errors - toastNotifications.addDanger({ - title: i18n.translate('discover.context.unableToLoadAnchorDocumentDescription', { - defaultMessage: 'Unable to load the anchor document', - }), - text: toMountPoint({error.message}), + }: { + indexPatternId: string; + anchorId: string; + tieBreakerField: string; + sort: [[string, SortDirection]]; + }) => { + const [[, sortDir]] = sort; + + context$.next({ anchorStatus: LoadingStatus.LOADING }); + return fetchAnchor(indexPatternId, anchorId, [ + fromPairs(sort), + { [tieBreakerField]: sortDir }, + ]) + .then( + (anchor: EsHitRecord): EsHitRecord => { + context$.next({ anchor, anchorStatus: LoadingStatus.LOADED }); + return anchor; + } + ) + .catch((error) => { + context$.next({ + anchorStatus: { reason: FailureReason.INVALID_TIEBREAKER }, + }); + toastNotifications.addDanger({ + title: i18n.translate('discover.context.unableToLoadAnchorDocumentDescription', { + defaultMessage: 'Unable to load the anchor document', + }), + text: toMountPoint({error.message}), + }); + throw error; }); - throw error; - } - ); - }, [fetchAnchor, state, toastNotifications, context$]); + }, + [context$, fetchAnchor, toastNotifications] + ); const fetchSurroundingRows = useCallback( - (type: SurrDocType) => { - const { - queryParameters: { indexPatternId, tieBreakerField }, - rows: { anchor }, - sort, - } = state; + (type, count, { indexPatternId, anchor, tieBreakerField, sort }: FetchSurrDocsParams) => { const filters = filterManager.getFilters(); - - const count = - type === 'successors' - ? state.queryParameters.successorCount - : state.queryParameters.predecessorCount; - - if (!tieBreakerField) { - // reject request - } - - // set loading const [[sortField, sortDir]] = sort; + context$.next({ [`${type}Status`]: LoadingStatus.LOADING }); return fetchSurroundingDocs( type, indexPatternId, @@ -119,12 +134,10 @@ export function useContextAppQuery({ count, filters ).then( - (documents: EsHitRecordList) => { - // set loaded - return documents; - }, - (error: Error) => { - // set error + (hits: EsHitRecordList) => + context$.next({ [type]: hits, [`${type}Status`]: LoadingStatus.LOADED }), + (error) => { + context$.next({ [`${type}Status`]: { reason: LoadingStatus.FAILED } }); toastNotifications.addDanger({ title: i18n.translate('discover.context.unableToLoadDocumentDescription', { defaultMessage: 'Unable to load documents', @@ -135,27 +148,37 @@ export function useContextAppQuery({ } ); }, - [toastNotifications, fetchSurroundingDocs, filterManager, state] + [context$, fetchSurroundingDocs, filterManager, toastNotifications] ); - const fetchAllRows = useCallback(() => { - fetchAnchorRow()?.then(() => { - Promise.all([fetchSurroundingRows('predecessors'), fetchSurroundingRows('successors')]).then( - ([predecessors, successors]) => { - context$.next({ - rows: { - ...state.rows, - all: [ - ...(predecessors || []), - ...(state.rows.anchor ? [state.rows.anchor] : []), - ...(successors || []), - ], - }, - }); - } - ); - }); - }, [fetchAnchorRow, fetchSurroundingRows, state, context$]); + const fetchContextRows = useCallback( + (predecessorCount: number, successorCount: number, params: FetchSurrDocsParams) => { + return Promise.all([ + fetchSurroundingRows('predecessors', predecessorCount, params), + fetchSurroundingRows('successors', successorCount, params), + ]); + }, + [fetchSurroundingRows] + ); + + const fetchAllRows = useCallback( + ( + predecessorCount: number, + successorCount: number, + { anchorId, ...restParams }: FetchAllRowsParams + ) => { + fetchAnchorRow({ + anchorId, + ...restParams, + }).then((anchor: EsHitRecord) => { + return fetchContextRows(predecessorCount, successorCount, { + anchor, + ...restParams, + }); + }); + }, + [fetchAnchorRow, fetchContextRows] + ); - return { context$, fetchAnchorRow, fetchAllRows }; + return { context$, fetchContextRows, fetchAllRows }; }