Merge branch 'master' of git.curoverse.com:arvados-workbench2 into 13862-get-current...
[arvados-workbench2.git] / src / store / tree-picker / tree-picker-actions.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { unionize, ofType, UnionOf } from "~/common/unionize";
6 import { TreeNode, initTreeNode, getNodeDescendants, getNodeDescendantsIds, getNodeValue, TreeNodeStatus, getNode } from '~/models/tree';
7 import { Dispatch } from 'redux';
8 import { RootState } from '~/store/store';
9 import { ServiceRepository } from '~/services/services';
10 import { FilterBuilder } from '~/services/api/filter-builder';
11 import { pipe } from 'lodash/fp';
12 import { ResourceKind } from '~/models/resource';
13 import { GroupContentsResource } from '../../services/groups-service/groups-service';
14 import { CollectionDirectory, CollectionFile } from '../../models/collection-file';
15 import { getTreePicker } from './tree-picker';
16 import { ProjectsTreePickerItem } from '~/views-components/projects-tree-picker/generic-projects-tree-picker';
17
18 export const treePickerActions = unionize({
19     LOAD_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(),
20     LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ id: string, nodes: Array<TreeNode<any>>, pickerId: string }>(),
21     TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ id: string, pickerId: string }>(),
22     ACTIVATE_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(),
23     DEACTIVATE_TREE_PICKER_NODE: ofType<{ pickerId: string }>(),
24     TOGGLE_TREE_PICKER_NODE_SELECTION: ofType<{ id: string, pickerId: string }>(),
25     EXPAND_TREE_PICKER_NODES: ofType<{ ids: string[], pickerId: string }>(),
26     RESET_TREE_PICKER: ofType<{ pickerId: string }>()
27 });
28
29 export type TreePickerAction = UnionOf<typeof treePickerActions>;
30
31 export const getProjectsTreePickerIds = (pickerId: string) => ({
32     home: `${pickerId}_home`,
33     shared: `${pickerId}_shared`,
34     favorites: `${pickerId}_favorites`,
35 });
36 export const initProjectsTreePicker = (pickerId: string) =>
37     async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
38         const { home, shared, favorites } = getProjectsTreePickerIds(pickerId);
39         dispatch<any>(initUserProject(home));
40         dispatch<any>(initSharedProject(shared));
41         dispatch<any>(initFavoritesProject(favorites));
42     };
43
44 interface ReceiveTreePickerDataParams<T> {
45     data: T[];
46     extractNodeData: (value: T) => { id: string, value: T, status?: TreeNodeStatus };
47     id: string;
48     pickerId: string;
49 }
50 export const receiveTreePickerData = <T>(params: ReceiveTreePickerDataParams<T>) =>
51     (dispatch: Dispatch) => {
52         const { data, extractNodeData, id, pickerId, } = params;
53         dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
54             id,
55             nodes: data.map(item => initTreeNode(extractNodeData(item))),
56             pickerId,
57         }));
58         dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
59     };
60
61 interface LoadProjectParams {
62     id: string;
63     pickerId: string;
64     includeCollections?: boolean;
65     includeFiles?: boolean;
66     loadShared?: boolean;
67 }
68 export const loadProject = (params: LoadProjectParams) =>
69     async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
70         const { id, pickerId, includeCollections = false, includeFiles = false, loadShared = false } = params;
71
72         dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId }));
73
74         const filters = pipe(
75             (fb: FilterBuilder) => includeCollections
76                 ? fb.addIsA('uuid', [ResourceKind.PROJECT, ResourceKind.COLLECTION])
77                 : fb.addIsA('uuid', [ResourceKind.PROJECT]),
78             fb => fb.getFilters(),
79         )(new FilterBuilder());
80
81         const { items } = await services.groupsService.contents(loadShared ? '' : id, { filters, excludeHomeProject: loadShared || undefined });
82
83         dispatch<any>(receiveTreePickerData<GroupContentsResource>({
84             id,
85             pickerId,
86             data: items,
87             extractNodeData: item => ({
88                 id: item.uuid,
89                 value: item,
90                 status: item.kind === ResourceKind.PROJECT
91                     ? TreeNodeStatus.INITIAL
92                     : includeFiles
93                         ? TreeNodeStatus.INITIAL
94                         : TreeNodeStatus.LOADED
95             }),
96         }));
97     };
98
99 export const loadCollection = (id: string, pickerId: string) =>
100     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
101         dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId }));
102
103         const picker = getTreePicker<ProjectsTreePickerItem>(pickerId)(getState().treePicker);
104         if (picker) {
105
106             const node = getNode(id)(picker);
107             if (node && 'kind' in node.value && node.value.kind === ResourceKind.COLLECTION) {
108
109                 const files = await services.collectionService.files(node.value.portableDataHash);
110                 const data = getNodeDescendants('')(files).map(node => node.value);
111
112                 dispatch<any>(receiveTreePickerData<CollectionDirectory | CollectionFile>({
113                     id,
114                     pickerId,
115                     data,
116                     extractNodeData: value => ({
117                         id: value.id,
118                         status: TreeNodeStatus.LOADED,
119                         value,
120                     }),
121                 }));
122             }
123         }
124     };
125
126
127 export const initUserProject = (pickerId: string) =>
128     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
129         const uuid = services.authService.getUuid();
130         if (uuid) {
131             dispatch(receiveTreePickerData({
132                 id: '',
133                 pickerId,
134                 data: [{ uuid, name: 'Projects' }],
135                 extractNodeData: value => ({
136                     id: value.uuid,
137                     status: TreeNodeStatus.INITIAL,
138                     value,
139                 }),
140             }));
141         }
142     };
143 export const loadUserProject = (pickerId: string, includeCollections = false, includeFiles = false) =>
144     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
145         const uuid = services.authService.getUuid();
146         if (uuid) {
147             dispatch(loadProject({ id: uuid, pickerId, includeCollections, includeFiles }));
148         }
149     };
150
151
152 export const initSharedProject = (pickerId: string) =>
153     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
154         dispatch(receiveTreePickerData({
155             id: '',
156             pickerId,
157             data: [{ uuid: 'Shared with me', name: 'Shared with me' }],
158             extractNodeData: value => ({
159                 id: value.uuid,
160                 status: TreeNodeStatus.INITIAL,
161                 value,
162             }),
163         }));
164     };
165
166 export const initFavoritesProject = (pickerId: string) =>
167     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
168         dispatch(receiveTreePickerData({
169             id: '',
170             pickerId,
171             data: [{ uuid: 'Favorites', name: 'Favorites' }],
172             extractNodeData: value => ({
173                 id: value.uuid,
174                 status: TreeNodeStatus.INITIAL,
175                 value,
176             }),
177         }));
178     };
179
180 interface LoadFavoritesProjectParams {
181     pickerId: string;
182     includeCollections?: boolean;
183     includeFiles?: boolean;
184 }
185 export const loadFavoritesProject = (params: LoadFavoritesProjectParams) =>
186     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
187         const { pickerId, includeCollections = false, includeFiles = false } = params;
188         const uuid = services.authService.getUuid();
189         if (uuid) {
190
191             const filters = pipe(
192                 (fb: FilterBuilder) => includeCollections
193                     ? fb.addIsA('headUuid', [ResourceKind.PROJECT, ResourceKind.COLLECTION])
194                     : fb.addIsA('headUuid', [ResourceKind.PROJECT]),
195                 fb => fb.getFilters(),
196             )(new FilterBuilder());
197
198             const { items } = await services.favoriteService.list(uuid, { filters });
199
200             dispatch<any>(receiveTreePickerData<GroupContentsResource>({
201                 id: 'Favorites',
202                 pickerId,
203                 data: items,
204                 extractNodeData: item => ({
205                     id: item.uuid,
206                     value: item,
207                     status: item.kind === ResourceKind.PROJECT
208                         ? TreeNodeStatus.INITIAL
209                         : includeFiles
210                             ? TreeNodeStatus.INITIAL
211                             : TreeNodeStatus.LOADED
212                 }),
213             }));
214         }
215     };