20031: Add split files into separate collection move/copy actions
[arvados.git] / src / store / collections / collection-partial-copy-actions.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 { FormErrors, initialize, startSubmit, stopSubmit } from 'redux-form';
8 import { resetPickerProjectTree } from 'store/project-tree-picker/project-tree-picker-actions';
9 import { dialogActions } from 'store/dialog/dialog-actions';
10 import { ServiceRepository } from 'services/services';
11 import { filterCollectionFilesBySelection } from '../collection-panel/collection-panel-files/collection-panel-files-state';
12 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
13 import { getCommonResourceServiceError, CommonResourceServiceError } from 'services/common-service/common-resource-service';
14 import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
15 import { initProjectsTreePicker } from "store/tree-picker/tree-picker-actions";
16 import { updateResources } from 'store/resources/resources-actions';
17 import { navigateTo } from 'store/navigation/navigation-action';
18
19 export const COLLECTION_PARTIAL_COPY_FORM_NAME = 'COLLECTION_PARTIAL_COPY_DIALOG';
20 export const COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION = 'COLLECTION_PARTIAL_COPY_TO_SELECTED_DIALOG';
21 export const COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS = 'COLLECTION_PARTIAL_COPY_TO_SEPARATE_DIALOG';
22
23 export interface CollectionPartialCopyToNewCollectionFormData {
24     name: string;
25     description: string;
26     projectUuid: string;
27 }
28
29 export interface CollectionPartialCopyToExistingCollectionFormData {
30     destination: {uuid: string, path?: string};
31 }
32
33 export interface CollectionPartialCopyToSeparateCollectionsFormData {
34     name: string;
35     projectUuid: string;
36 }
37
38 export const openCollectionPartialCopyToNewCollectionDialog = () =>
39     (dispatch: Dispatch, getState: () => RootState) => {
40         const currentCollection = getState().collectionPanel.item;
41         if (currentCollection) {
42             const initialData = {
43                 name: `Files extracted from: ${currentCollection.name}`,
44                 description: currentCollection.description,
45                 projectUuid: undefined
46             };
47             dispatch(initialize(COLLECTION_PARTIAL_COPY_FORM_NAME, initialData));
48             dispatch<any>(resetPickerProjectTree());
49             dispatch<any>(initProjectsTreePicker(COLLECTION_PARTIAL_COPY_FORM_NAME));
50             dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME, data: {} }));
51         }
52     };
53
54 export const copyCollectionPartialToNewCollection = ({ name, description, projectUuid }: CollectionPartialCopyToNewCollectionFormData) =>
55     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
56         const state = getState();
57         // Get current collection
58         const sourceCollection = state.collectionPanel.item;
59
60         if (sourceCollection) {
61             try {
62                 dispatch(startSubmit(COLLECTION_PARTIAL_COPY_FORM_NAME));
63                 dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
64
65                 // Get selected files
66                 const paths = filterCollectionFilesBySelection(state.collectionPanelFiles, true)
67                     .map(file => file.id.replace(new RegExp(`(^${sourceCollection.uuid})`), ''));
68
69                 // Copy files
70                 const updatedCollection = await services.collectionService.copyFiles(
71                     sourceCollection.portableDataHash,
72                     paths,
73                     {
74                         name,
75                         description,
76                         ownerUuid: projectUuid,
77                         uuid: undefined,
78                     },
79                     '/',
80                     false
81                 );
82                 dispatch(updateResources([updatedCollection]));
83                 dispatch<any>(navigateTo(updatedCollection.uuid))
84
85                 dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
86                 dispatch(snackbarActions.OPEN_SNACKBAR({
87                     message: 'New collection created.',
88                     hideDuration: 2000,
89                     kind: SnackbarKind.SUCCESS
90                 }));
91                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
92             } catch (e) {
93                 const error = getCommonResourceServiceError(e);
94                 if (error === CommonResourceServiceError.UNIQUE_NAME_VIOLATION) {
95                     dispatch(stopSubmit(COLLECTION_PARTIAL_COPY_FORM_NAME, { name: 'Collection with this name already exists.' } as FormErrors));
96                 } else if (error === CommonResourceServiceError.UNKNOWN) {
97                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
98                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not create a copy of collection', hideDuration: 2000, kind: SnackbarKind.ERROR }));
99                 } else {
100                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
101                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been copied but may contain incorrect files.', hideDuration: 2000, kind: SnackbarKind.ERROR }));
102                 }
103                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
104             }
105         }
106     };
107
108 export const openCollectionPartialCopyToExistingCollectionDialog = () =>
109     (dispatch: Dispatch, getState: () => RootState) => {
110         const currentCollection = getState().collectionPanel.item;
111         if (currentCollection) {
112             const initialData = {
113                 destination: {uuid: '', destinationPath: ''}
114             };
115             dispatch(initialize(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION, initialData));
116             dispatch<any>(resetPickerProjectTree());
117             dispatch<any>(initProjectsTreePicker(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
118             dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION, data: {} }));
119         }
120     };
121
122 export const copyCollectionPartialToExistingCollection = ({ destination }: CollectionPartialCopyToExistingCollectionFormData) =>
123     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
124         const state = getState();
125         // Get current collection
126         const sourceCollection = state.collectionPanel.item;
127
128         if (sourceCollection && destination.uuid) {
129             try {
130                 dispatch(startSubmit(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
131                 dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
132                 // Get selected files
133                 const paths = filterCollectionFilesBySelection(state.collectionPanelFiles, true)
134                     .map(file => file.id.replace(new RegExp(`(^${sourceCollection.uuid})`), ''));
135
136                 // Copy files
137                 const updatedCollection = await services.collectionService.copyFiles(sourceCollection.portableDataHash, paths, {uuid: destination.uuid}, destination.path || '/', false);
138                 dispatch(updateResources([updatedCollection]));
139
140                 dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION }));
141                 dispatch(snackbarActions.OPEN_SNACKBAR({
142                     message: 'Files has been copied to selected collection.',
143                     hideDuration: 2000,
144                     kind: SnackbarKind.SUCCESS
145                 }));
146                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
147             } catch (e) {
148                 const error = getCommonResourceServiceError(e);
149                 if (error === CommonResourceServiceError.UNKNOWN) {
150                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION }));
151                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not copy this files to selected collection', hideDuration: 2000, kind: SnackbarKind.ERROR }));
152                 }
153                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
154             }
155         }
156     };
157
158 export const openCollectionPartialCopyToSeparateCollectionsDialog = () =>
159     (dispatch: Dispatch, getState: () => RootState) => {
160         const currentCollection = getState().collectionPanel.item;
161         if (currentCollection) {
162             const initialData = {
163                 name: currentCollection.name,
164                 projectUuid: undefined
165             };
166             dispatch(initialize(COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS, initialData));
167             dispatch<any>(resetPickerProjectTree());
168             dispatch<any>(initProjectsTreePicker(COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS));
169             dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS, data: {} }));
170         }
171     };
172
173 export const copyCollectionPartialToSeparateCollections = ({ name, projectUuid }: CollectionPartialCopyToSeparateCollectionsFormData) =>
174     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
175         const state = getState();
176         // Get current collection
177         const sourceCollection = state.collectionPanel.item;
178
179         if (sourceCollection) {
180             try {
181                 dispatch(startSubmit(COLLECTION_PARTIAL_COPY_FORM_NAME));
182                 dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
183
184                 // Get selected files
185                 const paths = filterCollectionFilesBySelection(state.collectionPanelFiles, true)
186                     .map(file => file.id.replace(new RegExp(`(^${sourceCollection.uuid})`), ''));
187
188                 // Copy files
189                 const collections = await Promise.all(paths.map((path) =>
190                     services.collectionService.copyFiles(
191                         sourceCollection.portableDataHash,
192                         [path],
193                         {
194                             name: `File split from collection ${name}${path}`,
195                             ownerUuid: projectUuid,
196                             uuid: undefined,
197                         },
198                         '/',
199                         false
200                     )
201                 ));
202                 dispatch(updateResources(collections));
203
204                 dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
205                 dispatch(snackbarActions.OPEN_SNACKBAR({
206                     message: 'New collections created.',
207                     hideDuration: 2000,
208                     kind: SnackbarKind.SUCCESS
209                 }));
210                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
211             } catch (e) {
212                 const error = getCommonResourceServiceError(e);
213                 console.log(e, error);
214                 if (error === CommonResourceServiceError.UNIQUE_NAME_VIOLATION) {
215                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection from one or more files already exists', hideDuration: 2000, kind: SnackbarKind.ERROR }));
216                 } else if (error === CommonResourceServiceError.UNKNOWN) {
217                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
218                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not create a copy of collection', hideDuration: 2000, kind: SnackbarKind.ERROR }));
219                 } else {
220                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
221                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been copied but may contain incorrect files.', hideDuration: 2000, kind: SnackbarKind.ERROR }));
222                 }
223                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
224             }
225         }
226     };