From e07ae87ffcd6251f959a271303432b43b56274d3 Mon Sep 17 00:00:00 2001 From: ananzh Date: Tue, 29 Aug 2023 19:08:32 +0000 Subject: [PATCH] [Data Explorer][Discover 2.0] Add missing change interval on TimeChart and improve fetch Issue Resolve https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4847 Signed-off-by: ananzh --- .../application/components/chart/chart.tsx | 14 +- .../utils/state_management/discover_slice.tsx | 7 + .../view_components/canvas/discover_table.tsx | 7 +- .../view_components/utils/use_search.ts | 231 ++++++++---------- 4 files changed, 129 insertions(+), 130 deletions(-) diff --git a/src/plugins/discover/public/application/components/chart/chart.tsx b/src/plugins/discover/public/application/components/chart/chart.tsx index 75e33594fea..30b7b3894f7 100644 --- a/src/plugins/discover/public/application/components/chart/chart.tsx +++ b/src/plugins/discover/public/application/components/chart/chart.tsx @@ -15,6 +15,8 @@ import { TimechartHeader, TimechartHeaderBucketInterval } from './timechart_head import { DiscoverHistogram } from './histogram/histogram'; import { DiscoverServices } from '../../../build_services'; import { Chart } from './utils'; +import { useDiscoverContext } from '../../view_components/context'; +import { setInterval, useDispatch, useSelector } from '../../utils/state_management'; interface DiscoverChartProps { bucketInterval: TimechartHeaderBucketInterval; @@ -39,14 +41,18 @@ export const DiscoverChart = ({ services, showResetButton = false, }: DiscoverChartProps) => { + const { refetch$ } = useDiscoverContext(); const { from, to } = data.query.timefilter.timefilter.getTime(); const timeRange = { from: dateMath.parse(from)?.format('YYYY-MM-DDTHH:mm:ss.SSSZ') || '', to: dateMath.parse(to, { roundUp: true })?.format('YYYY-MM-DDTHH:mm:ss.SSSZ') || '', }; - - const onChangeInterval = () => {}; - + const { interval } = useSelector((state) => state.discover); + const dispatch = useDispatch(); + const onChangeInterval = (newInterval: string) => { + dispatch(setInterval(newInterval)); + refetch$.next(); + }; const timefilterUpdateHandler = useCallback( (ranges: { from: number; to: number }) => { data.query.timefilter.timefilter.setTime({ @@ -75,7 +81,7 @@ export const DiscoverChart = ({ timeRange={timeRange} options={search.aggs.intervalOptions} onChangeInterval={onChangeInterval} - stateInterval={'auto'} + stateInterval={interval || ''} /> )} diff --git a/src/plugins/discover/public/application/utils/state_management/discover_slice.tsx b/src/plugins/discover/public/application/utils/state_management/discover_slice.tsx index 73cc1af35e1..727f3010032 100644 --- a/src/plugins/discover/public/application/utils/state_management/discover_slice.tsx +++ b/src/plugins/discover/public/application/utils/state_management/discover_slice.tsx @@ -136,6 +136,12 @@ export const discoverSlice = createSlice({ sort: action.payload, }; }, + setInterval(state, action: PayloadAction) { + return { + ...state, + interval: action.payload, + }; + }, updateState(state, action: PayloadAction>) { return { ...state, @@ -159,6 +165,7 @@ export const { reorderColumn, setColumns, setSort, + setInterval, setState, updateState, setSavedSearchId, diff --git a/src/plugins/discover/public/application/view_components/canvas/discover_table.tsx b/src/plugins/discover/public/application/view_components/canvas/discover_table.tsx index f4e28a59b08..6720bc5171f 100644 --- a/src/plugins/discover/public/application/view_components/canvas/discover_table.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/discover_table.tsx @@ -29,7 +29,7 @@ interface Props { export const DiscoverTable = ({ history }: Props) => { const { services } = useOpenSearchDashboards(); const { filterManager } = services.data.query; - const { data$, indexPattern } = useDiscoverContext(); + const { data$, refetch$, indexPattern } = useDiscoverContext(); const [fetchState, setFetchState] = useState({ status: data$.getValue().status, rows: [], @@ -41,7 +41,10 @@ export const DiscoverTable = ({ history }: Props) => { const onRemoveColumn = (col: string) => dispatch(removeColumn(col)); const onSetColumns = (cols: string[]) => dispatch(setColumns({ timefield: indexPattern.timeFieldName, columns: cols })); - const onSetSort = (s: SortOrder[]) => dispatch(setSort(s)); + const onSetSort = (s: SortOrder[]) => { + dispatch(setSort(s)); + refetch$.next(); + }; const onAddFilter = useCallback( (field: IndexPatternField, values: string, operation: '+' | '-') => { const newFilters = opensearchFilters.generateFilters( diff --git a/src/plugins/discover/public/application/view_components/utils/use_search.ts b/src/plugins/discover/public/application/view_components/utils/use_search.ts index 50ad55bf404..44cf47f2c06 100644 --- a/src/plugins/discover/public/application/view_components/utils/use_search.ts +++ b/src/plugins/discover/public/application/view_components/utils/use_search.ts @@ -68,7 +68,7 @@ export type RefetchSubject = Subject; */ export const useSearch = (services: DiscoverServices) => { const [savedSearch, setSavedSearch] = useState(undefined); - const { savedSearch: savedSearchId, sort } = useSelector((state) => state.discover); + const { savedSearch: savedSearchId, sort, interval } = useSelector((state) => state.discover); const indexPattern = useIndexPattern(services); const { data, filterManager, getSavedSearchById, core, toastNotifications } = services; const timefilter = data.query.timefilter.timefilter; @@ -102,125 +102,123 @@ export const useSearch = (services: DiscoverServices) => { [shouldSearchOnPageLoad] ); const refetch$ = useMemo(() => new Subject(), []); - const sort$ = useMemo(() => new Subject(), []); - const fetch = useCallback( - async (sortArr: SortOrder[]) => { - if (!indexPattern) { - data$.next({ - status: shouldSearchOnPageLoad() ? ResultStatus.LOADING : ResultStatus.UNINITIALIZED, - }); - return; - } - - if (!validateTimeRange(timefilter.getTime(), toastNotifications)) { - return data$.next({ - status: ResultStatus.NO_RESULTS, - rows: [], - }); - } + const fetch = useCallback(async () => { + if (!indexPattern) { + data$.next({ + status: shouldSearchOnPageLoad() ? ResultStatus.LOADING : ResultStatus.UNINITIALIZED, + }); + return; + } - // Abort any in-progress requests before fetching again - if (fetchStateRef.current.abortController) fetchStateRef.current.abortController.abort(); - fetchStateRef.current.abortController = new AbortController(); - const histogramConfigs = indexPattern.timeFieldName - ? createHistogramConfigs(indexPattern, 'auto', data) - : undefined; - const searchSource = await updateSearchSource({ - indexPattern, - services, - sort: sortArr, - searchSource: savedSearch?.searchSource, - histogramConfigs, + if (!validateTimeRange(timefilter.getTime(), toastNotifications)) { + return data$.next({ + status: ResultStatus.NO_RESULTS, + rows: [], }); + } - try { - // Only show loading indicator if we are fetching when the rows are empty - if (fetchStateRef.current.rows?.length === 0) { - data$.next({ status: ResultStatus.LOADING }); - } + // Abort any in-progress requests before fetching again + if (fetchStateRef.current.abortController) fetchStateRef.current.abortController.abort(); + fetchStateRef.current.abortController = new AbortController(); + const histogramConfigs = indexPattern.timeFieldName + ? createHistogramConfigs(indexPattern, interval || 'auto', data) + : undefined; + const searchSource = await updateSearchSource({ + indexPattern, + services, + sort, + searchSource: savedSearch?.searchSource, + histogramConfigs, + }); - // Initialize inspect adapter for search source - inspectorAdapters.requests.reset(); - const title = i18n.translate('discover.inspectorRequestDataTitle', { - defaultMessage: 'data', - }); - const description = i18n.translate('discover.inspectorRequestDescription', { - defaultMessage: 'This request queries OpenSearch to fetch the data for the search.', - }); - const inspectorRequest = inspectorAdapters.requests.start(title, { description }); - inspectorRequest.stats(getRequestInspectorStats(searchSource)); - searchSource.getSearchRequestBody().then((body) => { - inspectorRequest.json(body); - }); + try { + // Only show loading indicator if we are fetching when the rows are empty + if (fetchStateRef.current.rows?.length === 0) { + data$.next({ status: ResultStatus.LOADING }); + } - // Execute the search - const fetchResp = await searchSource.fetch({ - abortSignal: fetchStateRef.current.abortController.signal, - }); + // Initialize inspect adapter for search source + inspectorAdapters.requests.reset(); + const title = i18n.translate('discover.inspectorRequestDataTitle', { + defaultMessage: 'data', + }); + const description = i18n.translate('discover.inspectorRequestDescription', { + defaultMessage: 'This request queries OpenSearch to fetch the data for the search.', + }); + const inspectorRequest = inspectorAdapters.requests.start(title, { description }); + inspectorRequest.stats(getRequestInspectorStats(searchSource)); + searchSource.getSearchRequestBody().then((body) => { + inspectorRequest.json(body); + }); - inspectorRequest - .stats(getResponseInspectorStats(fetchResp, searchSource)) - .ok({ json: fetchResp }); - const hits = fetchResp.hits.total as number; - const rows = fetchResp.hits.hits; - let bucketInterval = {}; - let chartData; - for (const row of rows) { - const fields = Object.keys(indexPattern.flattenHit(row)); - for (const fieldName of fields) { - fetchStateRef.current.fieldCounts[fieldName] = - (fetchStateRef.current.fieldCounts[fieldName] || 0) + 1; - } + // Execute the search + const fetchResp = await searchSource.fetch({ + abortSignal: fetchStateRef.current.abortController.signal, + }); + + inspectorRequest + .stats(getResponseInspectorStats(fetchResp, searchSource)) + .ok({ json: fetchResp }); + const hits = fetchResp.hits.total as number; + const rows = fetchResp.hits.hits; + let bucketInterval = {}; + let chartData; + for (const row of rows) { + const fields = Object.keys(indexPattern.flattenHit(row)); + for (const fieldName of fields) { + fetchStateRef.current.fieldCounts[fieldName] = + (fetchStateRef.current.fieldCounts[fieldName] || 0) + 1; } + } - if (histogramConfigs) { - const bucketAggConfig = histogramConfigs.aggs[1]; - const tabifiedData = tabifyAggResponse(histogramConfigs, fetchResp); - const dimensions = getDimensions(histogramConfigs, data); - if (dimensions) { - if (bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig)) { - bucketInterval = bucketAggConfig.buckets?.getInterval(); - } - // @ts-ignore tabifiedData is compatible but due to the way it is typed typescript complains - chartData = buildPointSeriesData(tabifiedData, dimensions); + if (histogramConfigs) { + const bucketAggConfig = histogramConfigs.aggs[1]; + const tabifiedData = tabifyAggResponse(histogramConfigs, fetchResp); + const dimensions = getDimensions(histogramConfigs, data); + if (dimensions) { + if (bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig)) { + bucketInterval = bucketAggConfig.buckets?.getInterval(); } + // @ts-ignore tabifiedData is compatible but due to the way it is typed typescript complains + chartData = buildPointSeriesData(tabifiedData, dimensions); } + } - fetchStateRef.current.fieldCounts = fetchStateRef.current.fieldCounts!; - fetchStateRef.current.rows = rows; - data$.next({ - status: rows.length > 0 ? ResultStatus.READY : ResultStatus.NO_RESULTS, - fieldCounts: fetchStateRef.current.fieldCounts, - hits, - rows, - bucketInterval, - chartData, - }); - } catch (error) { - // If the request was aborted then no need to surface this error in the UI - if (error instanceof Error && error.name === 'AbortError') return; + fetchStateRef.current.fieldCounts = fetchStateRef.current.fieldCounts!; + fetchStateRef.current.rows = rows; + data$.next({ + status: rows.length > 0 ? ResultStatus.READY : ResultStatus.NO_RESULTS, + fieldCounts: fetchStateRef.current.fieldCounts, + hits, + rows, + bucketInterval, + chartData, + }); + } catch (error) { + // If the request was aborted then no need to surface this error in the UI + if (error instanceof Error && error.name === 'AbortError') return; - data$.next({ - status: ResultStatus.NO_RESULTS, - rows: [], - }); + data$.next({ + status: ResultStatus.NO_RESULTS, + rows: [], + }); - data.search.showError(error as Error); - } - }, - [ - indexPattern, - timefilter, - toastNotifications, - data, - services, - savedSearch?.searchSource, - data$, - shouldSearchOnPageLoad, - inspectorAdapters.requests, - ] - ); + data.search.showError(error as Error); + } + }, [ + indexPattern, + interval, + timefilter, + toastNotifications, + data, + services, + savedSearch?.searchSource, + data$, + sort, + shouldSearchOnPageLoad, + inspectorAdapters.requests, + ]); useEffect(() => { const fetch$ = merge( @@ -229,14 +227,13 @@ export const useSearch = (services: DiscoverServices) => { timefilter.getFetch$(), timefilter.getTimeUpdate$(), timefilter.getAutoRefreshFetch$(), - data.query.queryString.getUpdates$(), - sort$ + data.query.queryString.getUpdates$() ).pipe(debounceTime(100)); const subscription = fetch$.subscribe(() => { (async () => { try { - await fetch(sort); + await fetch(); } catch (error) { core.fatalErrors.add(error as Error); } @@ -249,17 +246,7 @@ export const useSearch = (services: DiscoverServices) => { return () => { subscription.unsubscribe(); }; - }, [ - data$, - data.query.queryString, - filterManager, - refetch$, - sort, - sort$, - timefilter, - fetch, - core.fatalErrors, - ]); + }, [data$, data.query.queryString, filterManager, refetch$, timefilter, fetch, core.fatalErrors]); // Get savedSearch if it exists useEffect(() => { @@ -271,10 +258,6 @@ export const useSearch = (services: DiscoverServices) => { return () => {}; }, [getSavedSearchById, savedSearchId]); - useEffect(() => { - sort$.next(sort); - }, [sort, sort$]); - return { data$, refetch$,