From 9700822f13c7f81939e8344e799d2d30f749c2f7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Daniel=20Kuty=C5=82a?= Date: Fri, 28 Oct 2022 15:25:18 +0200 Subject: [PATCH] 19275: Fix for race conditions in the full text search bar MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła --- src/services/common-service/common-service.ts | 12 +++-- src/services/groups-service/groups-service.ts | 7 ++- src/store/search-bar/search-bar-actions.ts | 52 ++++++++++++------- .../search-bar/search-bar.tsx | 2 +- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/services/common-service/common-service.ts b/src/services/common-service/common-service.ts index bdae87ab..b8e7dc67 100644 --- a/src/services/common-service/common-service.ts +++ b/src/services/common-service/common-service.ts @@ -87,11 +87,13 @@ export class CommonService { return mapKeys ? CommonService.mapResponseKeys(response) : response.data; }) .catch(({ response }) => { - actions.progressFn(reqId, false); - const errors = CommonService.mapResponseKeys(response) as Errors; - errors.status = response.status; - actions.errorFn(reqId, errors, showErrors); - throw errors; + if (response) { + actions.progressFn(reqId, false); + const errors = CommonService.mapResponseKeys(response) as Errors; + errors.status = response.status; + actions.errorFn(reqId, errors, showErrors); + throw errors; + } }); } diff --git a/src/services/groups-service/groups-service.ts b/src/services/groups-service/groups-service.ts index dc6a798c..b69483cb 100644 --- a/src/services/groups-service/groups-service.ts +++ b/src/services/groups-service/groups-service.ts @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 +import { CancelToken } from 'axios'; import { snakeCase, camelCase } from "lodash"; import { CommonResourceService } from 'services/common-service/common-resource-service'; import { ListResults, ListArguments } from 'services/common-service/common-service'; @@ -41,7 +42,7 @@ export class GroupsService extends Tras super(serverApi, "groups", actions); } - async contents(uuid: string, args: ContentsArguments = {}, session?: Session): Promise> { + async contents(uuid: string, args: ContentsArguments = {}, session?: Session, cancelToken?: CancelToken): Promise> { const { filters, order, ...other } = args; const params = { ...other, @@ -56,6 +57,10 @@ export class GroupsService extends Tras cfg.headers = { 'Authorization': 'Bearer ' + session.token }; } + if (cancelToken) { + cfg.cancelToken = cancelToken; + } + const response = await CommonResourceService.defaultResponse( this.serverApi.get(this.resourceType + pathUrl, cfg), this.actions, false ); diff --git a/src/store/search-bar/search-bar-actions.ts b/src/store/search-bar/search-bar-actions.ts index 7d76ec69..8b03ddd7 100644 --- a/src/store/search-bar/search-bar-actions.ts +++ b/src/store/search-bar/search-bar-actions.ts @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 +import axios from "axios"; import { ofType, unionize, UnionOf } from "common/unionize"; import { GroupContentsResource, GroupContentsResourcePrefix } from 'services/groups-service/groups-service'; import { Dispatch } from 'redux'; @@ -62,13 +63,13 @@ export const loadRecentQueries = () => return recentQueries; }; -export const searchData = (searchValue: string) => +export const searchData = (searchValue: string, useCancel = false) => async (dispatch: Dispatch, getState: () => RootState) => { const currentView = getState().searchBar.currentView; dispatch(searchResultsPanelActions.CLEAR()); dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue)); if (searchValue.length > 0) { - dispatch(searchGroups(searchValue, 5)); + dispatch(searchGroups(searchValue, 5, useCancel)); if (currentView === SearchView.BASIC) { dispatch(searchBarActions.CLOSE_SEARCH_VIEW()); dispatch(navigateToSearchResults(searchValue)); @@ -203,26 +204,41 @@ export const submitData = (event: React.FormEvent) => } }; - -const searchGroups = (searchValue: string, limit: number) => +let cancelTokens: any[] = []; +const searchGroups = (searchValue: string, limit: number, useCancel = false) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const currentView = getState().searchBar.currentView; - if (searchValue || currentView === SearchView.ADVANCED) { - const { cluster: clusterId } = getAdvancedDataFromQuery(searchValue); - const sessions = getSearchSessions(clusterId, getState().auth.sessions); - const lists: ListResults[] = await Promise.all(sessions.map(session => { - const filters = queryToFilters(searchValue, session.apiRevision); - return services.groupsService.contents('', { - filters, - limit, - recursive: true - }, session); - })); - - const items = lists.reduce((items, list) => items.concat(list.items), [] as GroupContentsResource[]); - dispatch(searchBarActions.SET_SEARCH_RESULTS(items)); + if (cancelTokens.length > 0 && useCancel) { + cancelTokens.forEach(cancelToken => (cancelToken as any).cancel('New search request triggered.')); + cancelTokens = []; } + + setTimeout(async () => { + if (searchValue || currentView === SearchView.ADVANCED) { + const { cluster: clusterId } = getAdvancedDataFromQuery(searchValue); + const sessions = getSearchSessions(clusterId, getState().auth.sessions); + const lists: ListResults[] = await Promise.all(sessions.map((session, index) => { + cancelTokens.push(axios.CancelToken.source()); + const filters = queryToFilters(searchValue, session.apiRevision); + return services.groupsService.contents('', { + filters, + limit, + recursive: true + }, session, cancelTokens[index].token); + })); + + cancelTokens = []; + + const items = lists.reduce((items, list) => items.concat(list.items), [] as GroupContentsResource[]); + + if (lists.filter(list => !!(list as any).items).length !== lists.length) { + dispatch(searchBarActions.SET_SEARCH_RESULTS([])); + } else { + dispatch(searchBarActions.SET_SEARCH_RESULTS(items)); + } + } + }, 10); }; const buildQueryFromKeyMap = (data: any, keyMap: string[][]) => { diff --git a/src/views-components/search-bar/search-bar.tsx b/src/views-components/search-bar/search-bar.tsx index 327644ed..6a4d2a62 100644 --- a/src/views-components/search-bar/search-bar.tsx +++ b/src/views-components/search-bar/search-bar.tsx @@ -38,7 +38,7 @@ const mapStateToProps = ({ searchBar, form }: RootState): SearchBarDataProps => }; const mapDispatchToProps = (dispatch: Dispatch): SearchBarActionProps => ({ - onSearch: (valueSearch: string) => dispatch(searchData(valueSearch)), + onSearch: (valueSearch: string) => dispatch(searchData(valueSearch, true)), onChange: (event: React.ChangeEvent) => dispatch(changeData(event.target.value)), onSetView: (currentView: string) => dispatch(goToView(currentView)), onSubmit: (event: React.FormEvent) => dispatch(submitData(event)), -- 2.30.2