20251: Fix flaky collection file browser by using race-free state update callback
[arvados-workbench2.git] / src / store / store.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { createStore, applyMiddleware, compose, Middleware, combineReducers, Store, Action, Dispatch } from 'redux';
6 import { routerMiddleware, routerReducer } from "react-router-redux";
7 import thunkMiddleware from 'redux-thunk';
8 import { History } from "history";
9 import { handleRedirects } from '../common/redirect-to';
10
11 import { authReducer } from "./auth/auth-reducer";
12 import { authMiddleware } from "./auth/auth-middleware";
13 import { dataExplorerReducer } from './data-explorer/data-explorer-reducer';
14 import { detailsPanelReducer } from './details-panel/details-panel-reducer';
15 import { contextMenuReducer } from './context-menu/context-menu-reducer';
16 import { reducer as formReducer } from 'redux-form';
17 import { favoritesReducer } from './favorites/favorites-reducer';
18 import { snackbarReducer } from './snackbar/snackbar-reducer';
19 import { collectionPanelFilesReducer } from './collection-panel/collection-panel-files/collection-panel-files-reducer';
20 import { dataExplorerMiddleware } from "./data-explorer/data-explorer-middleware";
21 import { FAVORITE_PANEL_ID } from "./favorite-panel/favorite-panel-action";
22 import { PROJECT_PANEL_ID } from "./project-panel/project-panel-action";
23 import { ProjectPanelMiddlewareService } from "./project-panel/project-panel-middleware-service";
24 import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-middleware-service";
25 import { AllProcessesPanelMiddlewareService } from "./all-processes-panel/all-processes-panel-middleware-service";
26 import { collectionPanelReducer } from './collection-panel/collection-panel-reducer';
27 import { dialogReducer } from './dialog/dialog-reducer';
28 import { ServiceRepository } from "services/services";
29 import { treePickerReducer, treePickerSearchReducer } from './tree-picker/tree-picker-reducer';
30 import { treePickerSearchMiddleware } from './tree-picker/tree-picker-middleware';
31 import { resourcesReducer } from 'store/resources/resources-reducer';
32 import { propertiesReducer } from './properties/properties-reducer';
33 import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
34 import { TrashPanelMiddlewareService } from "store/trash-panel/trash-panel-middleware-service";
35 import { TRASH_PANEL_ID } from "store/trash-panel/trash-panel-action";
36 import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer';
37 import { processPanelReducer } from 'store/process-panel/process-panel-reducer';
38 import { SHARED_WITH_ME_PANEL_ID } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
39 import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
40 import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
41 import { runProcessPanelReducer } from 'store/run-process-panel/run-process-panel-reducer';
42 import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
43 import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
44 import { appInfoReducer } from 'store/app-info/app-info-reducer';
45 import { searchBarReducer } from './search-bar/search-bar-reducer';
46 import { SEARCH_RESULTS_PANEL_ID } from 'store/search-results-panel/search-results-panel-actions';
47 import { SearchResultsMiddlewareService } from './search-results-panel/search-results-middleware-service';
48 import { virtualMachinesReducer } from "store/virtual-machines/virtual-machines-reducer";
49 import { repositoriesReducer } from 'store/repositories/repositories-reducer';
50 import { keepServicesReducer } from 'store/keep-services/keep-services-reducer';
51 import { UserMiddlewareService } from 'store/users/user-panel-middleware-service';
52 import { USERS_PANEL_ID } from 'store/users/users-actions';
53 import { UserProfileGroupsMiddlewareService } from 'store/user-profile/user-profile-groups-middleware-service';
54 import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions'
55 import { GroupsPanelMiddlewareService } from 'store/groups-panel/groups-panel-middleware-service';
56 import { GROUPS_PANEL_ID } from 'store/groups-panel/groups-panel-actions';
57 import { GroupDetailsPanelMembersMiddlewareService } from 'store/group-details-panel/group-details-panel-members-middleware-service';
58 import { GroupDetailsPanelPermissionsMiddlewareService } from 'store/group-details-panel/group-details-panel-permissions-middleware-service';
59 import { GROUP_DETAILS_MEMBERS_PANEL_ID, GROUP_DETAILS_PERMISSIONS_PANEL_ID } from 'store/group-details-panel/group-details-panel-actions';
60 import { LINK_PANEL_ID } from 'store/link-panel/link-panel-actions';
61 import { LinkMiddlewareService } from 'store/link-panel/link-panel-middleware-service';
62 import { API_CLIENT_AUTHORIZATION_PANEL_ID } from 'store/api-client-authorizations/api-client-authorizations-actions';
63 import { ApiClientAuthorizationMiddlewareService } from 'store/api-client-authorizations/api-client-authorizations-middleware-service';
64 import { PublicFavoritesMiddlewareService } from 'store/public-favorites-panel/public-favorites-middleware-service';
65 import { PUBLIC_FAVORITE_PANEL_ID } from 'store/public-favorites-panel/public-favorites-action';
66 import { publicFavoritesReducer } from 'store/public-favorites/public-favorites-reducer';
67 import { linkAccountPanelReducer } from './link-account-panel/link-account-panel-reducer';
68 import { CollectionsWithSameContentAddressMiddlewareService } from 'store/collections-content-address-panel/collections-content-address-middleware-service';
69 import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
70 import { ownerNameReducer } from 'store/owner-name/owner-name-reducer';
71 import { SubprocessMiddlewareService } from 'store/subprocess-panel/subprocess-panel-middleware-service';
72 import { SUBPROCESS_PANEL_ID } from 'store/subprocess-panel/subprocess-panel-actions';
73 import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-panel-action';
74 import { Config } from 'common/config';
75 import { pluginConfig } from 'plugins';
76 import { MiddlewareListReducer } from 'common/plugintypes';
77 import { tooltipsMiddleware } from './tooltips/tooltips-middleware';
78 import { sidePanelReducer } from './side-panel/side-panel-reducer'
79 import { bannerReducer } from './banner/banner-reducer';
80
81 declare global {
82     interface Window {
83         __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
84     }
85 }
86
87 const composeEnhancers =
88     (process.env.NODE_ENV === 'development' &&
89         window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
90     compose;
91
92 export type RootState = ReturnType<ReturnType<typeof createRootReducer>>;
93
94 export type RootStore = Store<RootState, Action> & { dispatch: Dispatch<any> };
95
96 export function configureStore(history: History, services: ServiceRepository, config: Config): RootStore {
97     const rootReducer = createRootReducer(services);
98
99     const projectPanelMiddleware = dataExplorerMiddleware(
100         new ProjectPanelMiddlewareService(services, PROJECT_PANEL_ID)
101     );
102     const favoritePanelMiddleware = dataExplorerMiddleware(
103         new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID)
104     );
105     const allProcessessPanelMiddleware = dataExplorerMiddleware(
106         new AllProcessesPanelMiddlewareService(services, ALL_PROCESSES_PANEL_ID)
107     );
108     const trashPanelMiddleware = dataExplorerMiddleware(
109         new TrashPanelMiddlewareService(services, TRASH_PANEL_ID)
110     );
111     const searchResultsPanelMiddleware = dataExplorerMiddleware(
112         new SearchResultsMiddlewareService(services, SEARCH_RESULTS_PANEL_ID)
113     );
114     const sharedWithMePanelMiddleware = dataExplorerMiddleware(
115         new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID)
116     );
117     const workflowPanelMiddleware = dataExplorerMiddleware(
118         new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID)
119     );
120     const userPanelMiddleware = dataExplorerMiddleware(
121         new UserMiddlewareService(services, USERS_PANEL_ID)
122     );
123     const userProfileGroupsMiddleware = dataExplorerMiddleware(
124         new UserProfileGroupsMiddlewareService(services, USER_PROFILE_PANEL_ID)
125     );
126     const groupsPanelMiddleware = dataExplorerMiddleware(
127         new GroupsPanelMiddlewareService(services, GROUPS_PANEL_ID)
128     );
129     const groupDetailsPanelMembersMiddleware = dataExplorerMiddleware(
130         new GroupDetailsPanelMembersMiddlewareService(services, GROUP_DETAILS_MEMBERS_PANEL_ID)
131     );
132     const groupDetailsPanelPermissionsMiddleware = dataExplorerMiddleware(
133         new GroupDetailsPanelPermissionsMiddlewareService(services, GROUP_DETAILS_PERMISSIONS_PANEL_ID)
134     );
135     const linkPanelMiddleware = dataExplorerMiddleware(
136         new LinkMiddlewareService(services, LINK_PANEL_ID)
137     );
138     const apiClientAuthorizationMiddlewareService = dataExplorerMiddleware(
139         new ApiClientAuthorizationMiddlewareService(services, API_CLIENT_AUTHORIZATION_PANEL_ID)
140     );
141     const publicFavoritesMiddleware = dataExplorerMiddleware(
142         new PublicFavoritesMiddlewareService(services, PUBLIC_FAVORITE_PANEL_ID)
143     );
144     const collectionsContentAddress = dataExplorerMiddleware(
145         new CollectionsWithSameContentAddressMiddlewareService(services, COLLECTIONS_CONTENT_ADDRESS_PANEL_ID)
146     );
147     const subprocessMiddleware = dataExplorerMiddleware(
148         new SubprocessMiddlewareService(services, SUBPROCESS_PANEL_ID)
149     );
150     const redirectToMiddleware = (store: any) => (next: any) => (action: any) => {
151         const state = store.getState();
152
153         if (state.auth && state.auth.apiToken) {
154             handleRedirects(state.auth.apiToken, config);
155         }
156
157         return next(action);
158     };
159
160     let middlewares: Middleware[] = [
161         routerMiddleware(history),
162         thunkMiddleware.withExtraArgument(services),
163         authMiddleware(services),
164         tooltipsMiddleware(services),
165         projectPanelMiddleware,
166         favoritePanelMiddleware,
167         allProcessessPanelMiddleware,
168         trashPanelMiddleware,
169         searchResultsPanelMiddleware,
170         sharedWithMePanelMiddleware,
171         workflowPanelMiddleware,
172         userPanelMiddleware,
173         userProfileGroupsMiddleware,
174         groupsPanelMiddleware,
175         groupDetailsPanelMembersMiddleware,
176         groupDetailsPanelPermissionsMiddleware,
177         linkPanelMiddleware,
178         apiClientAuthorizationMiddlewareService,
179         publicFavoritesMiddleware,
180         collectionsContentAddress,
181         subprocessMiddleware,
182         treePickerSearchMiddleware
183     ];
184
185     const reduceMiddlewaresFn: (a: Middleware[],
186         b: MiddlewareListReducer) => Middleware[] = (a, b) => b(a, services);
187
188     middlewares = pluginConfig.middlewares.reduce(reduceMiddlewaresFn, middlewares);
189
190     const enhancer = composeEnhancers(applyMiddleware(redirectToMiddleware, ...middlewares));
191     return createStore(rootReducer, enhancer);
192 }
193
194 const createRootReducer = (services: ServiceRepository) => combineReducers({
195     auth: authReducer(services),
196     banner: bannerReducer,
197     collectionPanel: collectionPanelReducer,
198     collectionPanelFiles: collectionPanelFilesReducer,
199     contextMenu: contextMenuReducer,
200     dataExplorer: dataExplorerReducer,
201     detailsPanel: detailsPanelReducer,
202     dialog: dialogReducer,
203     favorites: favoritesReducer,
204     ownerName: ownerNameReducer,
205     publicFavorites: publicFavoritesReducer,
206     form: formReducer,
207     processLogsPanel: processLogsPanelReducer,
208     properties: propertiesReducer,
209     resources: resourcesReducer,
210     router: routerReducer,
211     snackbar: snackbarReducer,
212     treePicker: treePickerReducer,
213     treePickerSearch: treePickerSearchReducer,
214     fileUploader: fileUploaderReducer,
215     processPanel: processPanelReducer,
216     progressIndicator: progressIndicatorReducer,
217     runProcessPanel: runProcessPanelReducer,
218     appInfo: appInfoReducer,
219     searchBar: searchBarReducer,
220     virtualMachines: virtualMachinesReducer,
221     repositories: repositoriesReducer,
222     keepServices: keepServicesReducer,
223     linkAccountPanel: linkAccountPanelReducer,
224     sidePanel: sidePanelReducer
225 });