22141: Refactoring to reduce circular import dependencies
[arvados.git] / services / workbench2 / src / store / data-explorer / data-explorer-reducer.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import {
6     DataColumn,
7     resetSortDirection,
8     SortDirection,
9     toggleSortDirection,
10     DataColumns,
11 } from 'components/data-table/data-column';
12 import {
13     DataExplorerAction,
14     dataExplorerActions,
15     DataTableRequestState,
16 } from './data-explorer-action';
17 import {
18     DataTableFetchMode,
19 } from 'components/data-table/data-table';
20 import { DataTableFilters } from 'components/data-table-filters/data-table-filters';
21
22 export interface DataExplorer {
23     fetchMode: DataTableFetchMode;
24     columns: DataColumns<any, any>;
25     items: any[];
26     itemsAvailable: number;
27     loadingItemsAvailable: boolean;
28     page: number;
29     rowsPerPage: number;
30     rowsPerPageOptions: number[];
31     searchValue: string;
32     working?: boolean;
33     requestState: DataTableRequestState;
34     countRequestState: DataTableRequestState;
35     isNotFound: boolean;
36 }
37
38 export const initialDataExplorer: DataExplorer = {
39     fetchMode: DataTableFetchMode.PAGINATED,
40     columns: [],
41     items: [],
42     itemsAvailable: 0,
43     loadingItemsAvailable: false,
44     page: 0,
45     rowsPerPage: 50,
46     rowsPerPageOptions: [10, 20, 50, 100, 200, 500],
47     searchValue: '',
48     requestState: DataTableRequestState.IDLE,
49     countRequestState: DataTableRequestState.IDLE,
50     isNotFound: false,
51 };
52
53 export type DataExplorerState = Record<string, DataExplorer>;
54
55 export const dataExplorerReducer = (
56     state: DataExplorerState = {},
57     action: DataExplorerAction
58 ) => {
59     return dataExplorerActions.match(action, {
60         CLEAR: ({ id }) =>
61             update(state, id, (explorer) => ({
62                 ...explorer,
63                 page: 0,
64                 itemsAvailable: 0,
65                 items: [],
66             })),
67
68         RESET_PAGINATION: ({ id }) =>
69             update(state, id, (explorer) => ({ ...explorer, page: 0 })),
70
71         SET_FETCH_MODE: ({ id, fetchMode }) =>
72             update(state, id, (explorer) => ({ ...explorer, fetchMode })),
73
74         SET_COLUMNS: ({ id, columns }) => update(state, id, setColumns(columns)),
75
76         SET_FILTERS: ({ id, columnName, filters }) =>
77             update(state, id, mapColumns(setFilters(columnName, filters))),
78
79         SET_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) => (
80             update(state, id, (explorer) => {
81                 // Reject updates to pages other than current,
82                 //  DataExplorer middleware should retry
83                 // Also reject update if DE is pending, reduces flicker and appearance of race
84                 const updatedPage = page || 0;
85                 if (explorer.page === updatedPage && explorer.requestState === DataTableRequestState.PENDING) {
86                     return {
87                         ...explorer,
88                         items,
89                         itemsAvailable: itemsAvailable || explorer.itemsAvailable,
90                         page: updatedPage,
91                         rowsPerPage,
92                     }
93                 } else {
94                     return explorer;
95                 }
96             })
97         ),
98
99         SET_LOADING_ITEMS_AVAILABLE: ({ id, loadingItemsAvailable }) =>
100             update(state, id, (explorer) => ({
101                 ...explorer,
102                 loadingItemsAvailable,
103             })),
104
105         SET_ITEMS_AVAILABLE: ({ id, itemsAvailable }) =>
106             update(state, id, (explorer) => {
107                 // Ignore itemsAvailable updates if another countRequest is requested
108                 if (explorer.countRequestState === DataTableRequestState.PENDING) {
109                     return {
110                         ...explorer,
111                         itemsAvailable,
112                         loadingItemsAvailable: false,
113                     };
114                 } else {
115                     return explorer;
116                 }
117             }),
118
119         RESET_ITEMS_AVAILABLE: ({ id }) =>
120             update(state, id, (explorer) => ({ ...explorer, itemsAvailable: 0 })),
121
122         APPEND_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) =>
123             update(state, id, (explorer) => ({
124                 ...explorer,
125                 items: explorer.items.concat(items),
126                 itemsAvailable: explorer.itemsAvailable + (itemsAvailable || 0),
127                 page,
128                 rowsPerPage,
129             })),
130
131         SET_PAGE: ({ id, page }) =>
132             update(state, id, (explorer) => ({ ...explorer, page })),
133
134         SET_ROWS_PER_PAGE: ({ id, rowsPerPage }) =>
135             update(state, id, (explorer) => ({ ...explorer, rowsPerPage })),
136
137         SET_EXPLORER_SEARCH_VALUE: ({ id, searchValue }) =>
138             update(state, id, (explorer) => ({ ...explorer, searchValue })),
139
140         RESET_EXPLORER_SEARCH_VALUE: ({ id }) =>
141             update(state, id, (explorer) => ({ ...explorer, searchValue: '' })),
142
143         SET_REQUEST_STATE: ({ id, requestState }) =>
144             update(state, id, (explorer) => ({ ...explorer, requestState })),
145
146         SET_COUNT_REQUEST_STATE: ({ id, countRequestState }) =>
147             update(state, id, (explorer) => ({ ...explorer, countRequestState })),
148
149         TOGGLE_SORT: ({ id, columnName }) =>
150             update(state, id, mapColumns(toggleSort(columnName))),
151
152         TOGGLE_COLUMN: ({ id, columnName }) =>
153             update(state, id, mapColumns(toggleColumn(columnName))),
154
155         SET_IS_NOT_FOUND: ({ id, isNotFound }) =>
156             update(state, id, (explorer) => ({ ...explorer, isNotFound })),
157
158         default: () => state,
159     });
160 };
161 export const getDataExplorer = (state: DataExplorerState, id: string) => {
162     const returnValue = state[id] || initialDataExplorer;
163     return returnValue;
164 };
165
166 export const getSortColumn = <R>(dataExplorer: DataExplorer): DataColumn<any, R> | undefined =>
167     dataExplorer.columns.find(
168         (c: DataColumn<any, R>) => !!c.sort && c.sort.direction !== SortDirection.NONE
169     );
170
171 const update = (
172     state: DataExplorerState,
173     id: string,
174     updateFn: (dataExplorer: DataExplorer) => DataExplorer
175 ) => ({ ...state, [id]: updateFn(getDataExplorer(state, id)) });
176
177 const canUpdateColumns = (
178     prevColumns: DataColumns<any, any>,
179     nextColumns: DataColumns<any, any>
180 ) => {
181     if (prevColumns.length !== nextColumns.length) {
182         return true;
183     }
184     for (let i = 0; i < nextColumns.length; i++) {
185         const pc = prevColumns[i];
186         const nc = nextColumns[i];
187         if (pc.key !== nc.key || pc.name !== nc.name) {
188             return true;
189         }
190     }
191     return false;
192 };
193
194 const setColumns =
195     (columns: DataColumns<any, any>) => (dataExplorer: DataExplorer) => ({
196         ...dataExplorer,
197         columns: canUpdateColumns(dataExplorer.columns, columns)
198             ? columns
199             : dataExplorer.columns,
200     });
201
202 const mapColumns =
203     (mapFn: (column: DataColumn<any, any>) => DataColumn<any, any>) =>
204         (dataExplorer: DataExplorer) => ({
205             ...dataExplorer,
206             columns: dataExplorer.columns.map(mapFn),
207         });
208
209 const toggleSort = (columnName: string) => (column: DataColumn<any, any>) =>
210     column.name === columnName
211         ? toggleSortDirection(column)
212         : resetSortDirection(column);
213
214 const toggleColumn = (columnName: string) => (column: DataColumn<any, any>) =>
215     column.name === columnName
216         ? { ...column, selected: !column.selected }
217         : column;
218
219 const setFilters =
220     (columnName: string, filters: DataTableFilters) =>
221         (column: DataColumn<any, any>) =>
222             column.name === columnName ? { ...column, filters } : column;