Skip to content

Commit

Permalink
[Data Explorer][Discover 2.0] Add missing change interval on TimeChar…
Browse files Browse the repository at this point in the history
…t and improve fetch (#4850)

Issue Resolve
#4847

Signed-off-by: ananzh <ananzh@amazon.com>
  • Loading branch information
ananzh committed Aug 30, 2023
1 parent 4aa6d37 commit a239fc2
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 130 deletions.
14 changes: 10 additions & 4 deletions src/plugins/discover/public/application/components/chart/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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({
Expand Down Expand Up @@ -75,7 +81,7 @@ export const DiscoverChart = ({
timeRange={timeRange}
options={search.aggs.intervalOptions}
onChangeInterval={onChangeInterval}
stateInterval={'auto'}
stateInterval={interval || ''}
/>
</EuiFlexItem>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ export const discoverSlice = createSlice({
sort: action.payload,
};
},
setInterval(state, action: PayloadAction<string>) {
return {
...state,
interval: action.payload,
};
},
updateState(state, action: PayloadAction<Partial<DiscoverState>>) {
return {
...state,
Expand All @@ -159,6 +165,7 @@ export const {
reorderColumn,
setColumns,
setSort,
setInterval,
setState,
updateState,
setSavedSearchId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface Props {
export const DiscoverTable = ({ history }: Props) => {
const { services } = useOpenSearchDashboards<DiscoverViewServices>();
const { filterManager } = services.data.query;
const { data$, indexPattern } = useDiscoverContext();
const { data$, refetch$, indexPattern } = useDiscoverContext();
const [fetchState, setFetchState] = useState<SearchData>({
status: data$.getValue().status,
rows: [],
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export type RefetchSubject = Subject<SearchRefetch>;
*/
export const useSearch = (services: DiscoverServices) => {
const [savedSearch, setSavedSearch] = useState<SavedSearch | undefined>(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;
Expand Down Expand Up @@ -102,125 +102,123 @@ export const useSearch = (services: DiscoverServices) => {
[shouldSearchOnPageLoad]
);
const refetch$ = useMemo(() => new Subject<SearchRefetch>(), []);
const sort$ = useMemo(() => new Subject<SortOrder[]>(), []);

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(
Expand All @@ -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);
}
Expand All @@ -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(() => {
Expand All @@ -271,10 +258,6 @@ export const useSearch = (services: DiscoverServices) => {
return () => {};
}, [getSavedSearchById, savedSearchId]);

useEffect(() => {
sort$.next(sort);
}, [sort, sort$]);

return {
data$,
refetch$,
Expand Down

0 comments on commit a239fc2

Please sign in to comment.