20251: Fix flaky collection file browser by using race-free state update callback
[arvados-workbench2.git] / src / store / data-explorer / data-explorer-middleware.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { Dispatch } from 'redux';
6 import { RootState } from 'store/store';
7 import { ServiceRepository } from 'services/services';
8 import { Middleware } from 'redux';
9 import {
10     dataExplorerActions,
11     bindDataExplorerActions,
12     DataTableRequestState,
13 } from './data-explorer-action';
14 import { getDataExplorer } from './data-explorer-reducer';
15 import { DataExplorerMiddlewareService } from './data-explorer-middleware-service';
16
17 export const dataExplorerMiddleware =
18     (service: DataExplorerMiddlewareService): Middleware =>
19     (api) =>
20     (next) => {
21         const actions = bindDataExplorerActions(service.getId());
22
23         return (action) => {
24             const handleAction =
25                 <T extends { id: string }>(handler: (data: T) => void) =>
26                     (data: T) => {
27                         next(action);
28                         if (data.id === service.getId()) {
29                             handler(data);
30                         }
31                     };
32             dataExplorerActions.match(action, {
33                 SET_PAGE: handleAction(() => {
34                     api.dispatch(actions.REQUEST_ITEMS(false));
35                 }),
36                 SET_ROWS_PER_PAGE: handleAction(() => {
37                     api.dispatch(actions.REQUEST_ITEMS(true));
38                 }),
39                 SET_FILTERS: handleAction(() => {
40                     api.dispatch(actions.RESET_PAGINATION());
41                     api.dispatch(actions.REQUEST_ITEMS(true));
42                 }),
43                 TOGGLE_SORT: handleAction(() => {
44                     api.dispatch(actions.REQUEST_ITEMS(true));
45                 }),
46                 SET_EXPLORER_SEARCH_VALUE: handleAction(() => {
47                     api.dispatch(actions.RESET_PAGINATION());
48                     api.dispatch(actions.REQUEST_ITEMS(true));
49                 }),
50                 REQUEST_ITEMS: handleAction(({ criteriaChanged }) => {
51                     api.dispatch<any>(async (
52                         dispatch: Dispatch,
53                         getState: () => RootState,
54                         services: ServiceRepository
55                     ) => {
56                         while (true) {
57                             let de = getDataExplorer(
58                                 getState().dataExplorer,
59                                 service.getId()
60                             );
61                             switch (de.requestState) {
62                                 case DataTableRequestState.IDLE:
63                                     // Start a new request.
64                                     try {
65                                         dispatch(
66                                             actions.SET_REQUEST_STATE({
67                                                 requestState: DataTableRequestState.PENDING,
68                                             })
69                                         );
70                                         await service.requestItems(api, criteriaChanged);
71                                     } catch {
72                                         dispatch(
73                                             actions.SET_REQUEST_STATE({
74                                                 requestState: DataTableRequestState.NEED_REFRESH,
75                                             })
76                                         );
77                                     }
78                                     // Now check if the state is still PENDING, if it moved to NEED_REFRESH
79                                     // then we need to reissue requestItems
80                                     de = getDataExplorer(
81                                         getState().dataExplorer,
82                                         service.getId()
83                                     );
84                                     const complete =
85                                         de.requestState === DataTableRequestState.PENDING;
86                                     dispatch(
87                                         actions.SET_REQUEST_STATE({
88                                             requestState: DataTableRequestState.IDLE,
89                                         })
90                                     );
91                                     if (complete) {
92                                         return;
93                                     }
94                                     break;
95                                 case DataTableRequestState.PENDING:
96                                     // State is PENDING, move it to NEED_REFRESH so that when the current request finishes it starts a new one.
97                                     dispatch(
98                                         actions.SET_REQUEST_STATE({
99                                             requestState: DataTableRequestState.NEED_REFRESH,
100                                         })
101                                     );
102                                     return;
103                                 case DataTableRequestState.NEED_REFRESH:
104                                     // Nothing to do right now.
105                                     return;
106                             }
107                         }
108                     });
109                 }),
110                 default: () => next(action),
111             });
112         };
113     };