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