Merge branch '21128-toolbar-context-menu'
[arvados-workbench2.git] / 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 } from 'components/data-table/data-column';
11 import {
12     DataExplorerAction,
13     dataExplorerActions,
14     DataTableRequestState,
15 } from './data-explorer-action';
16 import {
17     DataColumns,
18     DataTableFetchMode,
19 } from 'components/data-table/data-table';
20 import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
21
22 export interface DataExplorer {
23     fetchMode: DataTableFetchMode;
24     columns: DataColumns<any, any>;
25     items: any[];
26     itemsAvailable: number;
27     page: number;
28     rowsPerPage: number;
29     rowsPerPageOptions: number[];
30     searchValue: string;
31     working?: boolean;
32     requestState: DataTableRequestState;
33 }
34
35 export const initialDataExplorer: DataExplorer = {
36     fetchMode: DataTableFetchMode.PAGINATED,
37     columns: [],
38     items: [],
39     itemsAvailable: 0,
40     page: 0,
41     rowsPerPage: 50,
42     rowsPerPageOptions: [10, 20, 50, 100, 200, 500],
43     searchValue: '',
44     requestState: DataTableRequestState.IDLE,
45 };
46
47 export type DataExplorerState = Record<string, DataExplorer>;
48
49 export const dataExplorerReducer = (
50     state: DataExplorerState = {},
51     action: DataExplorerAction
52 ) => {
53     return dataExplorerActions.match(action, {
54         CLEAR: ({ id }) =>
55             update(state, id, (explorer) => ({
56                 ...explorer,
57                 page: 0,
58                 itemsAvailable: 0,
59                 items: [],
60             })),
61
62         RESET_PAGINATION: ({ id }) =>
63             update(state, id, (explorer) => ({ ...explorer, page: 0 })),
64
65         SET_FETCH_MODE: ({ id, fetchMode }) =>
66             update(state, id, (explorer) => ({ ...explorer, fetchMode })),
67
68         SET_COLUMNS: ({ id, columns }) => update(state, id, setColumns(columns)),
69
70         SET_FILTERS: ({ id, columnName, filters }) =>
71             update(state, id, mapColumns(setFilters(columnName, filters))),
72
73         SET_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) => (
74             update(state, id, (explorer) => {
75                 // Reject updates to pages other than current,
76                 //  DataExplorer middleware should retry
77                 const updatedPage = page || 0;
78                 if (explorer.page === updatedPage) {
79                     return {
80                         ...explorer,
81                         items,
82                         itemsAvailable,
83                         page: updatedPage,
84                         rowsPerPage,
85                     }
86                 } else {
87                     return explorer;
88                 }
89             })
90         ),
91
92         APPEND_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) =>
93             update(state, id, (explorer) => ({
94                 ...explorer,
95                 items: state[id].items.concat(items),
96                 itemsAvailable: state[id].itemsAvailable + itemsAvailable,
97                 page,
98                 rowsPerPage,
99             })),
100
101         SET_PAGE: ({ id, page }) =>
102             update(state, id, (explorer) => ({ ...explorer, page })),
103
104         SET_ROWS_PER_PAGE: ({ id, rowsPerPage }) =>
105             update(state, id, (explorer) => ({ ...explorer, rowsPerPage })),
106
107         SET_EXPLORER_SEARCH_VALUE: ({ id, searchValue }) =>
108             update(state, id, (explorer) => ({ ...explorer, searchValue })),
109
110         RESET_EXPLORER_SEARCH_VALUE: ({ id }) =>
111             update(state, id, (explorer) => ({ ...explorer, searchValue: '' })),
112
113         SET_REQUEST_STATE: ({ id, requestState }) =>
114             update(state, id, (explorer) => ({ ...explorer, requestState })),
115
116         TOGGLE_SORT: ({ id, columnName }) =>
117             update(state, id, mapColumns(toggleSort(columnName))),
118
119         TOGGLE_COLUMN: ({ id, columnName }) =>
120             update(state, id, mapColumns(toggleColumn(columnName))),
121
122         default: () => state,
123     });
124 };
125 export const getDataExplorer = (state: DataExplorerState, id: string) => {
126     const returnValue = state[id] || initialDataExplorer;
127     return returnValue;
128 };
129
130 export const getSortColumn = <R>(dataExplorer: DataExplorer): DataColumn<any, R> | undefined =>
131     dataExplorer.columns.find(
132         (c: DataColumn<any, R>) => !!c.sort && c.sort.direction !== SortDirection.NONE
133     );
134
135 const update = (
136     state: DataExplorerState,
137     id: string,
138     updateFn: (dataExplorer: DataExplorer) => DataExplorer
139 ) => ({ ...state, [id]: updateFn(getDataExplorer(state, id)) });
140
141 const canUpdateColumns = (
142     prevColumns: DataColumns<any, any>,
143     nextColumns: DataColumns<any, any>
144 ) => {
145     if (prevColumns.length !== nextColumns.length) {
146         return true;
147     }
148     for (let i = 0; i < nextColumns.length; i++) {
149         const pc = prevColumns[i];
150         const nc = nextColumns[i];
151         if (pc.key !== nc.key || pc.name !== nc.name) {
152             return true;
153         }
154     }
155     return false;
156 };
157
158 const setColumns =
159     (columns: DataColumns<any, any>) => (dataExplorer: DataExplorer) => ({
160         ...dataExplorer,
161         columns: canUpdateColumns(dataExplorer.columns, columns)
162             ? columns
163             : dataExplorer.columns,
164     });
165
166 const mapColumns =
167     (mapFn: (column: DataColumn<any, any>) => DataColumn<any, any>) =>
168         (dataExplorer: DataExplorer) => ({
169             ...dataExplorer,
170             columns: dataExplorer.columns.map(mapFn),
171         });
172
173 const toggleSort = (columnName: string) => (column: DataColumn<any, any>) =>
174     column.name === columnName
175         ? toggleSortDirection(column)
176         : resetSortDirection(column);
177
178 const toggleColumn = (columnName: string) => (column: DataColumn<any, any>) =>
179     column.name === columnName
180         ? { ...column, selected: !column.selected }
181         : column;
182
183 const setFilters =
184     (columnName: string, filters: DataTableFilters) =>
185         (column: DataColumn<any, any>) =>
186             column.name === columnName ? { ...column, filters } : column;